1 module sbin.ut;
2 
3 import std.array : appender;
4 
5 import sbin.type;
6 import sbin.exception;
7 import sbin.serialize;
8 import sbin.deserialize;
9 import sbin.stdtypesrh;
10 
11 /*  A note about `stable_format` arrays below.
12 
13     The binary format of serializations has been stable since release v0.5.0.
14     The literal byte arrays in the unit tests are there to ensure that this
15     format remains stable across releases, and their values should not be
16     edited if at all possible. This is important where `sbin` is used for file
17     i/o.
18 
19     If a change in the format is unavoidable, a deserialization method for the
20     previous format(s) will need to be implemented, and the release notes will
21     have to clearly state the incompatibility with instructions for how to deal
22     with it. One way would be for `sbinDeserialize` to accept an additional
23     template parameter containing a version string.
24 
25     Semantic versioning (semver.org) applies to the binary compatibility of
26     APIs, but due to the scope of sbin we also keep the format stable across
27     at least minor and patch version increments. Meaning that if the format
28     changes then a major version bump is required. See also README.md.
29 */
30 
31 version (unittest) import std.algorithm : equal;
32 
33 @safe unittest
34 {
35     const a = 123;
36     immutable ubyte[] stable_format = [123, 0, 0, 0];
37     assert (a.sbinSerialize == stable_format);
38     assert (stable_format.sbinDeserialize!int == a);
39 }
40 
41 @safe unittest
42 {
43     enum V { v }
44     const a = V.v;
45     static assert (is(V == enum));
46     static assert (is(EnumNumType!V == ubyte));
47     immutable ubyte[] stable_format = [0];
48     assert (a.sbinSerialize == stable_format);
49     assert (stable_format.sbinDeserialize!V == a);
50 }
51 
52 @safe unittest
53 {
54     const a = 123;
55     auto as = a.sbinSerialize;
56     assert (as == [123, 0, 0, 0]);
57     int x;
58     sbinDeserialize(as, x);
59     assert (a == x);
60 }
61 
62 @safe unittest
63 {
64     auto s = "hello world";
65     immutable ubyte[] stable_format = [11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100];
66     assert (s.sbinSerialize == stable_format);
67     assert (equal(stable_format.sbinDeserialize!string, s));
68 }
69 
70 @safe unittest
71 {
72     immutable(int[]) a = [1, 2, 3, 2, 3, 2, 1];
73     immutable ubyte[] stable_format = [7, 1, 0, 0, 0,
74                                           2, 0, 0, 0,
75                                           3, 0, 0, 0,
76                                           2, 0, 0, 0,
77                                           3, 0, 0, 0,
78                                           2, 0, 0, 0,
79                                           1, 0, 0, 0];
80     assert (a.sbinSerialize == stable_format);
81     assert (stable_format.sbinDeserialize!(int[]) == a);
82 }
83 
84 @safe unittest
85 {
86     const int[5] a = [1, 2, 3, 2, 3];
87     immutable ubyte[] stable_format = [1, 0, 0, 0,
88                                        2, 0, 0, 0,
89                                        3, 0, 0, 0,
90                                        2, 0, 0, 0,
91                                        3, 0, 0, 0];
92     assert (a.sbinSerialize == stable_format);
93     assert (stable_format.sbinDeserialize!(typeof(a)) == a);
94 }
95 
96 @safe unittest
97 {
98     enum Color
99     {
100         black = "#000000",
101         red = "#ff0000",
102         green = "#00ff00",
103         blue = "#0000ff",
104         white = "#ffffff"
105     }
106 
107     enum Level { low, medium, high }
108 
109     struct Foo
110     {
111         ulong a;
112         float b, c;
113         ushort d;
114         string str;
115         Color color;
116         @sbinSkip int local = 42;
117     }
118     
119     const foo1 = Foo(10, 3.14, 2.17, 8, "s1", Color.red);
120 
121     //                  a              b         c       d
122     const foo1Size = ulong.sizeof + float.sizeof * 2 + ushort.sizeof +
123     //                            str                      color
124             (1 + foo1.str.length) + ubyte.sizeof; // 1 is length data because length < 127 (vluint pack)
125 
126     // color is ubyte because [EnumMembers!Color].length < ubyte.max
127 
128     const foo1Data = foo1.sbinSerialize;
129 
130     assert (foo1Data.length == foo1Size);
131     assert (foo1Data == [10, 0, 0, 0, 0, 0, 0, 0, 195, 245, 72, 64, 72, 225, 10, 64, 8, 0, 2, 115, 49, 1]);
132     assert (foo1Data.sbinDeserialize!Foo == foo1);
133     
134     const foo2 = Foo(2, 2.22, 2.22, 2, "str2", Color.green);
135 
136     const foo2Size = ulong.sizeof + float.sizeof * 2 + ushort.sizeof +
137             (1 + foo2.str.length) + ubyte.sizeof;
138 
139     const foo2Data = foo2.sbinSerialize;
140 
141     assert (foo2Data.length == foo2Size);
142     assert (foo2Data == [2, 0, 0, 0, 0, 0, 0, 0, 123, 20, 14, 64, 123, 20, 14, 64, 2, 0, 4, 115, 116, 114, 50, 2]);
143     assert (foo2Data.sbinDeserialize!Foo == foo2);
144 
145     struct Bar
146     {
147         ulong a;
148         float b;
149         Level level;
150         Foo[] foos;
151     }
152 
153     auto bar = Bar(123, 3.14, Level.high, [ foo1, foo2 ]);
154     
155     //                   a               b          level
156     const barSize = ulong.sizeof + float.sizeof + ubyte.sizeof +
157     //                                 foos
158                     (1 + foo1Size + foo2Size);
159     
160     const barData = bar.sbinSerialize;
161     assert (barData.length == barSize);
162     assert (barData == [123, 0, 0, 0, 0, 0, 0, 0, 195, 245, 72, 64, 2, 2, 10, 0,
163                         0, 0, 0, 0, 0, 0, 195, 245, 72, 64, 72, 225, 10, 64, 8,
164                         0, 2, 115, 49, 1, 2, 0, 0, 0, 0, 0, 0, 0, 123, 20, 14,
165                         64, 123, 20, 14, 64, 2, 0, 4, 115, 116, 114, 50, 2]);
166 
167     auto data = [
168         bar,
169         Bar(23,
170             31.4, Level.high,
171             [
172                 Foo(10, .11, .22, 50, "1one1"),
173                 Foo(20, .13, .25, 70, "2two2", Color.black),
174                 Foo(30, .15, .28, 30, "3three3", Color.white),
175             ]
176         ),
177     ];
178 
179     auto sdata = data.sbinSerialize;
180     assert (sdata == [2, 123, 0, 0, 0, 0, 0, 0, 0, 195, 245, 72, 64, 2, 2, 10,
181                       0, 0, 0, 0, 0, 0, 0, 195, 245, 72, 64, 72, 225, 10, 64, 8,
182                       0, 2, 115, 49, 1, 2, 0, 0, 0, 0, 0, 0, 0, 123, 20, 14, 64,
183                       123, 20, 14, 64, 2, 0, 4, 115, 116, 114, 50, 2, 23, 0, 0,
184                       0, 0, 0, 0, 0, 51, 51, 251, 65, 2, 3, 10, 0, 0, 0, 0, 0,
185                       0, 0, 174, 71, 225, 61, 174, 71, 97, 62, 50, 0, 5, 49,
186                       111, 110, 101, 49, 0, 20, 0, 0, 0, 0, 0, 0, 0, 184, 30, 5,
187                       62, 0, 0, 128, 62, 70, 0, 5, 50, 116, 119, 111, 50, 0, 30,
188                       0, 0, 0, 0, 0, 0, 0, 154, 153, 25, 62, 41, 92, 143, 62,
189                       30, 0, 7, 51, 116, 104, 114, 101, 101, 51, 4]);
190     assert ( equal(sdata.sbinDeserialize!(Bar[]), data));
191     data[0].foos[1].d = 12_345;
192     assert (!equal(sdata.sbinDeserialize!(Bar[]), data));
193 }
194 
195 @safe unittest
196 {
197     struct S
198     {
199         @sbinSkip int* p;
200     }
201 
202     auto s1 = S(new int(42));
203 
204     auto data = s1.sbinSerialize;
205     assert(data.length == 0);
206 
207     auto s2 = data.sbinDeserialize!S;
208     assert(s2.p == null);
209 }
210 
211 @safe unittest
212 {
213     static void foo(int a=123, string b="hello")
214     { assert (a==123); assert (b=="hello"); }
215 
216     auto a = ParameterDefaults!foo;
217 
218     import std.typecons : tuple;
219     const sa = tuple(a).sbinSerialize;
220     assert (sa == [123, 0, 0, 0, 5, 104, 101, 108, 108, 111]);
221 
222     Parameters!foo b;
223     b = sa.sbinDeserialize!(typeof(tuple(b)));
224     assert (a == b);
225     foo(b);
226 
227     a[0] = 234;
228     a[1] = "okda";
229     auto sn = tuple(a).sbinSerialize;
230     assert (sn == [234, 0, 0, 0, 4, 111, 107, 100, 97]);
231 
232     sn.sbinDeserialize(b);
233 
234     assert (b[0] == 234);
235     assert (b[1] == "okda");
236 }
237 
238 @safe unittest
239 {
240     auto a = [1,2,3,4];
241     auto as = a.sbinSerialize;
242     assert (as == [4, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0]);
243     auto as_tr1 = as[0..$-3];
244     assertThrown!SBinDeserializeEmptyRangeException(as_tr1.sbinDeserialize!(typeof(a)));
245     auto as_tr2 = as ~ as;
246     assertThrown!SBinDeserializeException(as_tr2.sbinDeserialize!(typeof(a)));
247 }
248 
249 @safe unittest
250 {
251     auto a = ["hello" : 123, "ok" : 43];
252     auto as = a.sbinSerialize;
253     assert (as == [2, 2, 111, 107, 43, 0, 0, 0, 5, 104, 101, 108, 108, 111, 123, 0, 0, 0]);
254 
255     auto b = as.sbinDeserialize!(typeof(a));
256     assert (b["hello"] == 123);
257     assert (b["ok"] == 43);
258 }
259 
260 unittest
261 {
262     static struct X
263     {
264         string[int] one;
265         int[string] two;
266     }
267 
268     auto a = X([3: "hello", 8: "abc"], ["ok": 1, "no": 2]);
269     auto b = X([8: "abc", 15: "ololo"], ["zb": 10]);
270 
271     const as = a.sbinSerialize;
272     assert (as == [2, 3, 0, 0, 0, 5, 104, 101, 108, 108, 111, 8, 0, 0, 0, 3, 97,
273                    98, 99, 2, 2, 111, 107, 1, 0, 0, 0, 2, 110, 111, 2, 0, 0, 0]);
274     const bs = b.sbinSerialize;
275     assert (bs == [2, 8, 0, 0, 0, 3, 97, 98, 99, 15, 0, 0, 0, 5, 111, 108, 111,
276                    108, 111, 1, 2, 122, 98, 10, 0, 0, 0]);
277 
278     auto c = as.sbinDeserialize!X;
279 
280     import std.algorithm : sort;
281     assert (equal(sort(a.one.keys.dup), sort(c.one.keys.dup)));
282     assert (equal(sort(a.one.values.dup), sort(c.one.values.dup)));
283 
284     bs.sbinDeserialize(c);
285 
286     assert (equal(sort(b.one.keys.dup), sort(c.one.keys.dup)));
287     assert (equal(sort(b.one.values.dup), sort(c.one.values.dup)));
288 }
289 
290 @safe unittest
291 {
292     enum T { one, two, three }
293     T[] a;
294     with(T) a = [one, two, three, two, three, two, one];
295     const as = a.sbinSerialize;
296     assert (as == [7, 0, 1, 2, 1, 2, 1, 0]);
297 
298     auto b = as.sbinDeserialize!(typeof(a));
299     assert (equal(a, b));
300 }
301 
302 @safe unittest
303 {
304     enum T { one="one", two="2", three="III" }
305     T[] a;
306     with(T) a = [one, two, three, two, three, two, one];
307     const as = a.sbinSerialize;
308     assert (as == [7, 0, 1, 2, 1, 2, 1, 0]);
309 
310     assert (as.length == 7 + 1);
311 
312     auto b = as.sbinDeserialize!(typeof(a));
313     assert (equal(a, b));
314 }
315 
316 @safe unittest
317 {
318     const int ai = 543;
319     auto as = "hello";
320 
321     import std.typecons : tuple;
322     auto buf = sbinSerialize(tuple(ai, as));
323     assert (buf == [31, 2, 0, 0, 5, 104, 101, 108, 108, 111]);
324 
325     int bi;
326     string bs;
327     sbinDeserialize(buf, bi, bs);
328 
329     assert (ai == bi);
330     assert (bs == as);
331 }
332 
333 @safe unittest
334 {
335     const int ai = 543;
336     auto as = "hello";
337 
338     auto buf = appender!(ubyte[]);
339     sbinSerialize(buf, ai, as);
340     assert (buf.data == [31, 2, 0, 0, 5, 104, 101, 108, 108, 111]);
341 
342     int bi;
343     string bs;
344     sbinDeserialize(buf.data, bi, bs);
345 
346     assert (ai == bi);
347     assert (bs == as);
348 }
349 
350 @safe unittest
351 {
352     static struct ImplaceAppender(Arr)
353     {
354         Arr _data;
355         size_t cur;
356 
357         this(Arr arr) { _data = arr; }
358 
359         @safe @nogc pure nothrow
360         {
361             void put(E)(E e)
362                 if (is(typeof(_data[0] = e)))
363             {
364                 _data[cur] = e;
365                 cur++;
366             }
367 
368             bool filled() const @property
369             { return cur == data.length; }
370 
371             inout(Arr) data() inout { return _data[0..cur]; }
372 
373             void clear() { cur = 0; }
374         }
375     }
376 
377     alias Buffer = ImplaceAppender!(ubyte[]);
378 
379     static assert (isOutputRange!(Buffer, ubyte));
380 
381     enum State
382     {
383         one   = "ONE",
384         two   = "TWO",
385         three = "THREE",
386     }
387 
388     struct Cell
389     {
390         ulong id;
391         float volt, temp;
392         ushort soc, soh;
393         string strData;
394         State state;
395     }
396 
397     struct Line
398     {
399         ulong id;
400         float volt, curr;
401         Cell[] cells;
402     }
403 
404     auto lines = [
405         Line(123,
406             3.14, 2.17,
407             [
408                 Cell(1, 1.1, 2.2, 5, 8, "one", State.one),
409                 Cell(2, 1.3, 2.5, 7, 9, "two", State.two),
410                 Cell(3, 1.5, 2.8, 3, 7, "three", State.three),
411             ]
412         ),
413         Line(23,
414             31.4, 21.7,
415             [
416                 Cell(10, .11, .22, 50, 80, "1one1", State.two),
417                 Cell(20, .13, .25, 70, 90, "2two2", State.three),
418                 Cell(30, .15, .28, 30, 70, "3three3", State.one),
419             ]
420         ),
421     ];
422 
423     ubyte[300] bdata;
424     auto buffer = Buffer(bdata[]);
425 
426     () @nogc { buffer.sbinSerialize(lines); }();
427 
428     assert (buffer.data == [2, 123, 0, 0, 0, 0, 0, 0, 0, 195, 245, 72, 64, 72, 225, 10, 64, 3, 1, 0, 0, 0, 0, 0, 0, 0,
429                             205, 204, 140, 63, 205, 204, 12, 64, 5, 0, 8, 0, 3, 111, 110, 101, 0, 2, 0, 0, 0, 0, 0, 0,
430                             0, 102, 102, 166, 63, 0, 0, 32, 64, 7, 0, 9, 0, 3, 116, 119, 111, 1, 3, 0, 0, 0, 0, 0, 0, 0,
431                             0, 0, 192, 63, 51, 51, 51, 64, 3, 0, 7, 0, 5, 116, 104, 114, 101, 101, 2, 23, 0, 0, 0, 0, 0,
432                             0, 0, 51, 51, 251, 65, 154, 153, 173, 65, 3, 10, 0, 0, 0, 0, 0, 0, 0, 174, 71, 225, 61, 174,
433                             71, 97, 62, 50, 0, 80, 0, 5, 49, 111, 110, 101, 49, 1, 20, 0, 0, 0, 0, 0, 0, 0, 184, 30, 5,
434                             62, 0, 0, 128, 62, 70, 0, 90, 0, 5, 50, 116, 119, 111, 50, 2, 30, 0, 0, 0, 0, 0, 0, 0, 154,
435                             153, 25, 62, 41, 92, 143, 62, 30, 0, 70, 0, 7, 51, 116, 104, 114, 101, 101, 51, 0]);
436     assert (equal(buffer.data.sbinDeserialize!(typeof(lines)), lines));
437 }
438 
439 @safe unittest
440 {
441     static bool ser, deser;
442     static struct Foo
443     {
444         ulong id;
445         ulong sbinCustomRepr() const @property
446         {
447             ser = true;
448             return id;
449         }
450         static Foo sbinFromCustomRepr(ulong v)
451         {
452             deser = true;
453             return Foo(v);
454         }
455     }
456 
457     auto foo = Foo(12);
458 
459     immutable ubyte[] stable_format = [12, 0, 0, 0, 0, 0, 0, 0];
460     assert (foo.sbinSerialize == stable_format);
461     assert (stable_format.sbinDeserialize!Foo == foo);
462 
463     assert (ser);
464     assert (deser);
465 }
466 
467 @safe unittest
468 {
469     static bool ser, deser;
470     static class Foo
471     {
472         ulong id;
473         this(ulong v) @safe { id = v; }
474         ulong sbinCustomRepr() const @safe
475         {
476             ser = true;
477             return id;
478         }
479         static Foo sbinFromCustomRepr()(auto ref const ulong v) @safe
480         {
481             deser = true;
482             return new Foo(v);
483         }
484     }
485 
486     auto foo = new Foo(12);
487 
488     immutable ubyte[] stable_format = [12, 0, 0, 0, 0, 0, 0, 0];
489     assert (foo.sbinSerialize == stable_format);
490     assert (stable_format.sbinDeserialize!Foo.id == 12);
491     assert (ser);
492     assert (deser);
493 
494     Foo[] fooArr;
495     foreach (i; 0 .. 10)
496         fooArr ~= new Foo(i);
497 
498     import std.algorithm : map;
499 
500     immutable ubyte[] stable_format2 = [10, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
501                                         0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0,
502                                         0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0,
503                                         0, 0, 0];
504     assert (fooArr.sbinSerialize == stable_format2);
505     auto fooArr2 = stable_format2.sbinDeserialize!(Foo[]);
506     assert (equal(fooArr.map!"a.id", fooArr2.map!"a.id"));
507 }
508 
509 @safe unittest
510 {
511     // for classes need
512     // T sbinCustomRepr() const
513     // static Foo sbinCustomDeserialize(T repr)
514     static class Foo
515     {
516         ulong id;
517         this(ulong v) { id = v; }
518     }
519 
520     auto foo = new Foo(12);
521 
522     static assert (!is(typeof(foo.sbinSerialize)));
523     static assert (!is(typeof(foo.sbinSerialize.sbinDeserialize!Foo.id)));
524 }
525 
526 @safe unittest
527 {
528     import std.bitmanip : bitfields;
529 
530     static struct Foo
531     {
532         mixin(bitfields!(
533             bool, "a", 1,
534             bool, "b", 1,
535             ubyte, "c", 4,
536             ubyte, "d", 2
537         ));
538     }
539 
540     static assert (Foo.sizeof == 1);
541 
542     Foo foo;
543     foo.a = true;
544     foo.b = false;
545     foo.c = 9;
546     foo.d = 3;
547 
548     assert (foo.a);
549     assert (foo.b == false);
550     assert (foo.c == 9);
551     assert (foo.d == 3);
552 
553     auto sfoo = foo.sbinSerialize;
554 
555     assert (sfoo.length == 1);
556     assert (sfoo == [229]);
557 
558     auto bar = sfoo.sbinDeserialize!Foo;
559 
560     assert (bar.a);
561     assert (bar.b == false);
562     assert (bar.c == 9);
563     assert (bar.d == 3);
564 
565     assert (foo == bar);
566 }
567 
568 @safe unittest
569 {
570     struct Foo
571     {
572         ubyte[] a, b;
573     }
574 
575     auto arr = cast(ubyte[])[1,2,3,4,5,6];
576     const foo = Foo(arr, arr[0..2]);
577     assert (foo.a.ptr == foo.b.ptr);
578     
579     immutable ubyte[] stable_format = [6, 1, 2, 3, 4, 5, 6, 2, 1, 2];
580     assert (foo.sbinSerialize == stable_format);
581     const foo2 = stable_format.sbinDeserialize!Foo;
582     assert (foo == foo2);
583     assert (foo.a.ptr != foo2.a.ptr);
584     assert (foo.b.ptr != foo2.b.ptr);
585     assert (foo2.a.ptr != foo2.b.ptr);
586 }
587 
588 unittest
589 {
590     struct Foo { void[] a; }
591     auto foo = Foo("hello".dup);
592     immutable ubyte[] stable_format = [5, 104, 101, 108, 108, 111];
593     assert (foo.sbinSerialize == stable_format);
594     auto foo2 = stable_format.sbinDeserialize!Foo;
595     assert (equal(cast(ubyte[])foo.a, cast(ubyte[])foo2.a));
596 }
597 
598 unittest
599 {
600     struct Foo { void[5] a; }
601     auto foo = Foo(cast(void[5])"hello");
602     immutable ubyte[] stable_format = [104, 101, 108, 108, 111];
603     assert (foo.sbinSerialize == stable_format);
604     auto foo2 = stable_format.sbinDeserialize!Foo;
605     assert (equal(cast(ubyte[])foo.a, cast(ubyte[])foo2.a));
606 }
607 
608 unittest
609 {
610     struct Foo { void[] a; void[5] b; }
611     auto foo = Foo("hello".dup, cast(void[5])"world");
612     immutable ubyte[] stable_format = [5, 104, 101, 108, 108, 111, 119, 111, 114, 108, 100];
613     assert (foo.sbinSerialize == stable_format);
614     auto foo2 = stable_format.sbinDeserialize!Foo;
615     assert (equal(cast(ubyte[])foo.a, cast(ubyte[])foo2.a));
616     assert (equal(cast(ubyte[])foo.b, cast(ubyte[])foo2.b));
617 }
618 
619 unittest
620 {
621     import std.variant : Algebraic;
622 
623     struct Foo
624     {
625         Algebraic!(int, float, string) data;
626         this(int a) { data = a; }
627         this(float a) { data = a; }
628         this(string a) { data = a; }
629     }
630 
631     auto foo = Foo(12);
632     static assert (!__traits(compiles, foo.sbinSerialize.sbinDeserialize!Foo));
633 }
634 
635 version (allowRawUnions)
636 {
637     @safe unittest
638     {
639         import std.algorithm : max;
640 
641         union Union
642         {
643             float fval;
644             byte ival;
645         }
646 
647         static assert (Union.init.sizeof == max(float.sizeof, byte.sizeof));
648 
649         Union u;
650         u.ival = 114;
651         assert (u.ival == 114);
652 
653         immutable ubyte[] stable_format = [114, 0, 192, 127];
654         assert (u.sbinSerialize == stable_format);
655         const su = stable_format.sbinDeserialize!Union;
656         assert (su.ival == 114);
657     }
658 }
659 
660 unittest
661 {
662     auto buf = appender!(ubyte[]);
663 
664     struct Foo1 { void[] a; void[5] b; }
665     auto foo1 = Foo1("hello".dup, cast(void[5])"world");
666 
667     sbinSerialize(buf, foo1);
668     assert (buf.data == [5, 104, 101, 108, 108, 111, 119, 111, 114, 108, 100]);
669 
670     static struct Foo2
671     {
672         import std.bitmanip : bitfields;
673         mixin(bitfields!(
674             bool, "a", 1,
675             bool, "b", 1,
676             ubyte, "c", 4,
677             ubyte, "d", 2
678         ));
679     }
680 
681     Foo2 foo2;
682     foo2.a = true;
683     foo2.b = false;
684     foo2.c = 9;
685     foo2.d = 3;
686 
687     sbinSerialize(buf, foo2);
688 
689     auto data = buf.data;
690     assert (data == [5, 104, 101, 108, 108, 111, 119, 111, 114, 108, 100, 229]);
691 
692     auto dsfoo1 = sbinDeserializePart!Foo1(data);
693     const dsfoo2 = sbinDeserializePart!Foo2(data);
694 
695     assert (data.empty);
696 
697     assert (equal(cast(ubyte[])foo1.a, cast(ubyte[])dsfoo1.a));
698     assert (equal(cast(ubyte[])foo1.b, cast(ubyte[])dsfoo1.b));
699     assert (dsfoo2.a);
700     assert (dsfoo2.b == false);
701     assert (dsfoo2.c == 9);
702     assert (dsfoo2.d == 3);
703 }
704 
705 @safe unittest
706 {
707     enum Label { good, bad }
708     static struct Pos { int x, y; }
709     static struct Point { Pos position; Label label; }
710 
711     Point[2] val = [
712         Point(Pos(3,7), Label.good),
713         Point(Pos(9,5), Label.bad),
714     ];
715     auto data = sbinSerialize(val);
716     assert (data == [3, 0, 0, 0, 7, 0, 0, 0, 0, 9, 0, 0, 0, 5, 0, 0, 0, 1]);
717 
718     assert (data.length == 18);
719 
720     import std.algorithm : canFind;
721 
722     {
723         bool throws;
724         try auto dsv = sbinDeserialize!(Point[2])(data[0..$-3]);
725         catch (SBinDeserializeEmptyRangeException e)
726         {
727             throws = true;
728             assert (e.msg.canFind("root.elem[1].position.y.byte[2]:int 2/4"), e.msg);
729         }
730         assert (throws);
731     }
732     {
733         bool throws;
734         try const dsv = sbinDeserialize!(Point[2])(data[0..$-1]);
735         catch (SBinDeserializeEmptyRangeException e)
736         {
737             throws = true;
738             assert (e.msg.canFind("root.elem[1].label.byte[0]:Label 0/1"), e.msg);
739         }
740         assert (throws);
741     }
742     {
743         bool throws;
744         try const dsv = sbinDeserialize!(Point[2])(data[0..$/2+1]);
745         catch (SBinDeserializeEmptyRangeException e)
746         {
747             throws = true;
748             assert (e.msg.canFind("root.elem[1].position.x.byte[1]:int 1/4"), e.msg);
749         }
750         assert (throws);
751     }
752     {
753         bool throws;
754         try const dsv = sbinDeserialize!(Point[2])(data[0..$/2-1]);
755         catch (SBinDeserializeEmptyRangeException e)
756         {
757             throws = true;
758             assert (e.msg.canFind("root.elem[0].label.byte[0]:Label 0/1"), e.msg);
759         }
760         assert (throws);
761     }
762 }
763 
764 @safe unittest
765 {
766     ushort[] val = [10,12,14,15];
767 
768     auto data = sbinSerialize(val);
769     assert (data == [4, 10, 0, 12, 0, 14, 0, 15, 0]);
770 
771     assert (data.length == 9);
772 
773     import std.algorithm : canFind;
774 
775     {
776         bool throws;
777         try const dsv = sbinDeserialize!(ushort[])(data[0..$-3]);
778         catch (SBinDeserializeEmptyRangeException e)
779         {
780             throws = true;
781             assert (e.msg.canFind("root.elem[2].byte[1]:ushort 1/2"), e.msg);
782         }
783         assert (throws);
784     }
785 
786     {
787         bool throws;
788         try const dsv = sbinDeserialize!(ushort[])(data[0..0]);
789         catch (SBinDeserializeEmptyRangeException e)
790         {
791             throws = true;
792             assert (e.msg.canFind("root.length:vluint 0/10"), e.msg);
793         }
794         assert (throws);
795     }
796 }
797 
798 @safe unittest
799 {
800     short[] value;
801     auto rng = sbinSerialize(value);
802     assert (rng == [0]);
803     assert (rng.length == 1);
804     assert (rng[0] == 0);
805 
806     assert (sbinDeserialize!(short[])(rng).length == 0);
807 }
808 
809 @safe unittest
810 {
811     vlint[] value = [vlint(1),vlint(2),vlint(3)];
812     auto rng = sbinSerialize(value);
813     assert (rng.length == 4);
814     assert (rng[0] == 3);
815     assert (rng[1] == 2);
816     assert (rng[2] == 4);
817     assert (rng[3] == 6);
818 
819     assert (value ==  sbinDeserialize!(typeof(value))(rng));
820 }
821 
822 @safe unittest
823 {
824     vlint[] value = [vlint(1),vlint(-130),vlint(3)];
825     auto rng = sbinSerialize(value);
826     assert (rng.length == 5);
827     assert (rng[0] == 3);
828     assert (rng[1] == 2);
829     //assert (rng[2] == 4);
830     assert (rng[4] == 6);
831     assert (rng == [3, 2, 131, 2, 6]);
832 
833     assert (value ==  sbinDeserialize!(typeof(value))(rng));
834 }
835 
836 @safe unittest
837 {
838     vluint[] value = [vluint(1),vluint(2),vluint(3)];
839     auto rng = sbinSerialize(value);
840     assert (rng.length == 4);
841     assert (rng[0] == 3);
842     assert (rng[1] == 1);
843     assert (rng[2] == 2);
844     assert (rng[3] == 3);
845 
846     assert (value == sbinDeserialize!(typeof(value))(rng));
847 }
848 
849 unittest
850 {
851     import std.bitmanip;
852     import std.random : uniform;
853     import std.range : iota;
854     import std.algorithm : map;
855     import std.array : array;
856 
857     // 273 random bits.
858     BitArray b = [0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
859                   0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0,
860                   1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1,
861                   0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0,
862                   1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
863                   0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1,
864                   0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0,
865                   0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0,
866                   1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1,
867                   1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1,
868                   0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1];
869 
870     static struct BitArrayWrap
871     {
872         BitArray arr;
873         this(BitArray a) { arr = a; }
874         this(Repr r) { arr = BitArray(r.data, r.bitcount); }
875 
876         static struct Repr
877         {
878             vluint bitcount;
879             void[] data;
880         }
881 
882         Repr sbinCustomRepr() @property const
883         { return Repr(vluint(arr.length), cast(void[])arr.dup); }
884 
885         static BitArrayWrap sbinFromCustomRepr(Repr r)
886         { return BitArrayWrap(r); }
887     }
888 
889     immutable ubyte[] stable_format = [145, 2, 40, 78, 215, 143, 77, 12, 42, 60, 164, 101, 132,
890                                        114, 188, 53, 58, 249, 88, 202, 201, 53, 159, 251, 57,
891                                        163, 202, 10, 33, 70, 43, 39, 187, 246, 155, 79, 207, 1,
892                                        0, 0, 0, 0, 0];
893     assert (equal(sbinSerialize(BitArrayWrap(b)), stable_format));
894     const t1 = sbinDeserialize!BitArrayWrap(stable_format).arr;
895 
896     assert (b == t1);
897 
898     static struct BitArrayWrap2
899     {
900         vluint bitcount;
901         void[] data;
902 
903         this(BitArray ba) { bitcount = ba.length; data = cast(void[])ba; }
904     }
905 
906     assert (equal(sbinSerialize(BitArrayWrap2(b)), stable_format));
907     const pft2 = sbinDeserialize!BitArrayWrap2(stable_format);
908 
909     const t2 = BitArray(pft2.data.dup, pft2.bitcount);
910 
911     assert (b == t2);
912 
913     import std.datetime;
914 
915     alias RH = CombineReprHandler!(BitArrayRH, SysTimeAsHNSecsRH!true);
916 
917     assert (sbinSerialize!RH(b) == stable_format);
918     auto t3 = sbinDeserialize!(RH, BitArray)(stable_format);
919 
920     assert (b == t3);
921 
922     struct Foo
923     {
924         string name;
925         BitArray data;
926         SysTime tm;
927     }
928 
929     auto foo = Foo("hello", b, SysTime(DateTime(2018, 1, 1, 10, 30, 0), UTC()));
930 
931     const foo_bytes = sbinSerialize!RH(foo);
932     assert (foo_bytes == [5, 104, 101, 108, 108, 111, 145, 2, 40, 78, 215, 143, 77, 12, 42,
933                           60, 164, 101, 132, 114, 188, 53, 58, 249, 88, 202, 201, 53, 159,
934                           251, 57, 163, 202, 10, 33, 70, 43, 39, 187, 246, 155, 79, 207, 1,
935                           0, 0, 0, 0, 0, 0, 196, 124, 156, 2, 81, 213, 8]);
936 
937     auto foo2 = sbinDeserialize!(RH, Foo)(foo_bytes);
938 
939     assert (foo.name == foo2.name);
940     assert (foo.data == foo2.data);
941     assert (foo.tm == foo2.tm);
942     assert (foo == foo2);
943 }
944 
945 static if (__VERSION__ >= 2097)
946 {
947     import std.sumtype;
948 
949     alias UT1 = std.sumtype.SumType!(string, int);
950 
951     //@safe // sumtype opAssign not safe
952     unittest
953     {
954         const a = "hello";
955 
956         auto val1 = UT1(a);
957 
958         assert (val1.match!(v => is(typeof(v) == string)));
959 
960         auto data = sbinSerialize(val1);
961 
962         assert (data == [0, 5, 104, 101, 108, 108, 111]);
963 
964         auto val2 = sbinDeserialize!UT1(data);
965 
966         assert (val1 == val2);
967         assert (val2.match!(v => is(typeof(v) == string)));
968     }
969 
970     unittest
971     {
972         const a = 42;
973 
974         auto val1 = UT1(a);
975 
976         assert (val1.match!(v => is(typeof(v) == int)));
977 
978         auto data = sbinSerialize(val1);
979 
980         assert (data == [1, 42, 0, 0, 0]);
981 
982         auto val2 = sbinDeserialize!UT1(data);
983 
984         assert (val1 == val2);
985         assert (val2.match!(v => is(typeof(v) == int)));
986     }
987 
988     alias UT2 = std.sumtype.SumType!(typeof(null), byte, byte[3]);
989 
990     unittest
991     {
992         auto val1 = [UT2(null), UT2(6), UT2([11,12,13])];
993         auto data = sbinSerialize(val1);
994         assert (data == [3, 0, 1, 6, 2, 11, 12, 13]);
995         auto val2 = sbinDeserialize!(UT2[])(data);
996         assert (val1 == val2);
997     }
998 
999     alias UT3 = std.sumtype.SumType!(typeof(null), byte, std.sumtype.This[]);
1000 
1001     unittest
1002     {
1003         auto val1 = UT3([
1004             UT3(42),
1005             UT3(null),
1006             UT3([ UT3(null), UT3(65) ]),
1007             UT3(12)
1008         ]);
1009 
1010         auto data = sbinSerialize(val1);
1011         assert (data == [2, 4, 1, 42, 0, 2, 2, 0, 1, 65, 1, 12]);
1012         auto val2 = sbinDeserialize!UT3(data);
1013         assert (val1 == val2);
1014     }
1015 
1016     import std : Nullable;
1017 
1018     unittest
1019     {
1020         alias NB = Nullable!ubyte;
1021 
1022         alias SNB = std.sumtype.SumType!(typeof(null), ubyte);
1023 
1024         const val1 = [ NB.init, NB(11), NB(12), NB.init, NB(13) ];
1025 
1026         alias RH = NullableAsSumTypeRH;
1027 
1028         assert (RH.repr(NB.init) == SNB(null));
1029         assert (RH.repr(NB(12)) == SNB(12));
1030         assert (RH.fromRepr(SNB(null)) == NB.init);
1031         assert (RH.fromRepr(SNB(21)) == NB(21));
1032 
1033         import sbin.repr;
1034 
1035         static assert (hasSerializeRepr!(RH, NB));
1036         static assert (hasDeserializeRepr!(RH, NB, SNB));
1037         static assert (hasRepr!(RH, NB));
1038 
1039         const data = sbinSerialize!RH(val1);
1040 
1041         assert (data == [5, 0, 1,11, 1,12, 0, 1,13]);
1042 
1043         const val2 = sbinDeserialize!(RH, NB[])(data);
1044 
1045         assert (val1 == val2);
1046     }
1047 
1048     unittest
1049     {
1050         static struct F1 { ubyte value; }
1051         static struct F2 { ubyte value; }
1052         static struct Foo
1053         {
1054             static struct Bar(T)
1055             {
1056                 string name;
1057                 Nullable!T value;
1058             }
1059 
1060             alias BF1 = Bar!F1;
1061             alias BF2 = Bar!F2;
1062 
1063             BF1[] f1s;
1064             BF2[] f2s;
1065         }
1066 
1067         alias RH = NullableAsSumTypeRH;
1068 
1069         const val1 = Foo(
1070             [
1071                 Foo.BF1("ok", Nullable!F1(F1(12))),
1072                 Foo.BF1("da", Nullable!F1(F1(32))),
1073                 Foo.BF1("ne", Nullable!(F1).init),
1074             ],
1075             [
1076                 Foo.BF2("o", Nullable!F2(F2(5))),
1077                 Foo.BF2("t", Nullable!(F2).init),
1078                 Foo.BF2("o", Nullable!F2(F2(7))),
1079             ]
1080         );
1081 
1082         const data = sbinSerialize!RH(val1);
1083         //const data = sbinSerialize(val1);
1084 
1085         assert (data == [
1086             3,
1087                 2, 111, 107,    1, 12,
1088                 2, 100,  97,    1, 32,
1089                 2, 110, 101,    0,
1090             3,
1091                 1, 111,         1, 5,
1092                 1, 116,         0,
1093                 1, 111,         1, 7
1094         ]);
1095 
1096         const val2 = sbinDeserialize!(RH, Foo)(data);
1097         //const val2 = sbinDeserialize!Foo(data);
1098         assert (val1 == val2);
1099     }
1100 
1101     unittest
1102     {
1103         import std.datetime;
1104 
1105         alias RH = CombineReprHandler!(SysTimeAsHNSecsRH!false, NullableAsSumTypeRH);
1106 
1107         alias NB = Nullable!ubyte;
1108 
1109         alias S = std.sumtype.SumType!(NB, SysTime);
1110 
1111         const val1 = [ S(NB.init), S(NB(11)), S(NB.init), S(NB(33)), S(SysTime(0)) ];
1112 
1113         const data = sbinSerialize!RH(val1);
1114 
1115         assert (data == [5, 0,0, 0,1,11, 0,0, 0,1,33, 1,0,0,0,0,0,0,0,0]);
1116 
1117         const val2 = sbinDeserialize!(RH, S[])(data);
1118 
1119         assert (val1 == val2);
1120     }
1121 }