1 /**
2  * Основной модуль сериализатора
3  *
4  * Copyright: (c) 2015-2017, Milofon Project.
5  * License: Subject to the terms of the BSD license, as written in the included LICENSE.txt file.
6  * Author: <m.galanin@milofon.org> Maksim Galanin
7  * Date: 2018-01-28
8  */
9 
10 module dango.service.serializer.core;
11 
12 
13 public
14 {
15     import proped : Properties;
16 }
17 
18 
19 private
20 {
21     import std.algorithm.comparison : max;
22     import std.traits : isSigned, isUnsigned, isBoolean,
23            isNumeric, isFloatingPoint, isArray, ForeachType,
24            isStaticArray;
25     import std.format : fmt = format;
26     import std.array : appender;
27     import std.exception : enforceEx;
28     import std.conv : to;
29 
30     import vibe.data.serialization :
31         vSerialize = serialize,
32         vDeserialize = deserialize;
33 }
34 
35 
36 /**
37  * Основной класс сериализатор
38  */
39 abstract class Serializer
40 {
41     /**
42      * Сериализация объекта языка в массив байт
43      * Params:
44      * object = Объект для преобразования
45      * Return: массив байт
46      */
47     ubyte[] serializeObject(T)(T object)
48     {
49         return serialize(marshalObject!T(object));
50     }
51 
52     /**
53      * Десериализация массива байт в объект языка
54      * Params:
55      * bytes = Массив байт
56      * Return: T
57      */
58     T deserializeObject(T)(ubyte[] bytes)
59     {
60         return unmarshalObject!T(deserialize(bytes));
61     }
62 
63     /**
64      * Десериализация массива байт в UniNode
65      * Params:
66      * bytes = Массив байт
67      * Return: UniNode
68      */
69     UniNode deserialize(ubyte[] bytes);
70 
71     /**
72      * Сериализация UniNode в массив байт
73      * Params:
74      * node = Данные в UniNode
75      * Return: массив байт
76      */
77     ubyte[] serialize(UniNode node);
78 
79     /**
80      * Инициализация сериализатора при помощи
81      * объекта настроек
82      * Params:
83      * config = Объект настроек
84      */
85     void initialize(Properties config);
86 }
87 
88 
89 /**
90  * Преобразует объекты языка в UniNode
91  * Params:
92  * object = Объект для преобразования
93  * Return: UniNode
94  */
95 UniNode marshalObject(T)(T object)
96 {
97     return vSerialize!(UniNodeSerializer, T)(object);
98 }
99 
100 
101 /**
102  * Преобразует UniNode в объекты языка
103  * Params:
104  * object = UniNode
105  * Return: T
106  */
107 T unmarshalObject(T)(UniNode node)
108 {
109     return vDeserialize!(UniNodeSerializer, T, UniNode)(node);
110 }
111 
112 
113 /**
114  * Проверка на целочисленное знаковое число
115  */
116 template isSignedNumeric(T)
117 {
118     enum isSignedNumeric = isNumeric!T && isSigned!T && !isFloatingPoint!T;
119 }
120 
121 
122 /**
123  * Проверка на целочисленное без знвковое число
124  */
125 template isUnsignedNumeric(T)
126 {
127     enum isUnsignedNumeric = isNumeric!T && isUnsigned!T && !isFloatingPoint!T;
128 }
129 
130 
131 /**
132  * Проверка на соотвествие бинарным данным
133  */
134 template isRawData(T)
135 {
136     enum isRawData = isArray!T && is(ForeachType!T == ubyte);
137 }
138 
139 
140 /**
141  * Универсальная структура для хранения данных
142  */
143 struct UniNode
144 {
145     enum Type
146     {
147         nil,
148         boolean,
149         unsigned,
150         signed,
151         floating,
152         text,
153         raw,
154         array,
155         object
156     }
157 
158 
159     union Via
160     {
161         bool boolean;
162         ulong uinteger;
163         long integer;
164         real floating;
165         ubyte[] raw;
166         UniNode[] array;
167         UniNode[string] map;
168     }
169 
170 
171     Via via;
172     Type type;
173 
174 
175     this(Type type)
176     {
177         this.type = type;
178     }
179 
180 
181     this(typeof(null))
182     {
183         this(Type.nil);
184     }
185 
186 
187     unittest
188     {
189         auto node = UniNode();
190         assert (node.type == Type.nil);
191     }
192 
193 
194     this(bool value)
195     {
196         this(Type.boolean);
197         via.boolean = value;
198     }
199 
200 
201     unittest
202     {
203         auto node = UniNode(false);
204         assert (node.type == Type.boolean);
205         assert (node.get!bool == false);
206     }
207 
208 
209     this(T)(T value) if (isSignedNumeric!T)
210     {
211         this(Type.signed);
212         via.uinteger = value;
213     }
214 
215 
216     unittest
217     {
218         import std.meta : AliasSeq;
219         foreach (TT; AliasSeq!(byte, short, int, long))
220         {
221             TT v = -11;
222             auto node = UniNode(v);
223             assert (node.type == Type.signed);
224             assert (is (typeof(node.get!TT) == TT));
225             assert (node.get!TT == -11);
226         }
227     }
228 
229 
230     this(T)(T value) if (isUnsignedNumeric!T)
231     {
232         this(Type.unsigned);
233         via.uinteger = value;
234     }
235 
236 
237     unittest
238     {
239         import std.meta : AliasSeq;
240         foreach (TT; AliasSeq!(ubyte, ushort, uint, ulong))
241         {
242             TT v = 11;
243             auto node = UniNode(v);
244             assert (node.type == Type.unsigned);
245             assert (is (typeof(node.get!TT) == TT));
246             assert (node.get!TT == 11);
247         }
248     }
249 
250 
251     this(T)(T value) if (isFloatingPoint!T)
252     {
253         this(Type.floating);
254         via.floating = value;
255     }
256 
257 
258     unittest
259     {
260         import std.meta : AliasSeq;
261         foreach (TT; AliasSeq!(float, double))
262         {
263             TT v = 11.11;
264             auto node = UniNode(v);
265             assert (node.type == Type.floating);
266             assert (is (typeof(node.get!TT) == TT));
267             assert (node.get!TT == cast(TT)11.11);
268         }
269     }
270 
271 
272     this(T)(T value) if (isRawData!T)
273     {
274         this(Type.raw);
275         static if (isStaticArray!T)
276             via.raw = value.dup;
277         else
278             via.raw = value;
279     }
280 
281 
282     unittest
283     {
284         ubyte[] dynArr = [1, 2, 3];
285         auto node = UniNode(dynArr);
286         assert (node.type == Type.raw);
287         assert (is(typeof(node.get!(ubyte[])) == ubyte[]));
288         assert (node.get!(ubyte[]) == [1, 2, 3]);
289 
290         ubyte[3] stArr = [1, 2, 3];
291         node = UniNode(stArr);
292         assert (node.type == Type.raw);
293         assert (is(typeof(node.get!(ubyte[3])) == ubyte[3]));
294         assert (node.get!(ubyte[3]) == [1, 2, 3]);
295     }
296 
297 
298     this(string value)
299     {
300         this(Type.text);
301         via.raw = cast(ubyte[])value;
302     }
303 
304 
305     unittest
306     {
307         string str = "hello";
308         auto node = UniNode(str);
309         assert(node.type == Type.text);
310         assert (is(typeof(node.get!(string)) == string));
311         assert (node.get!(string) == "hello");
312     }
313 
314 
315     this(UniNode[] value)
316     {
317         this(Type.array);
318         via.array = value;
319     }
320 
321 
322     this(UniNode[string] value)
323     {
324         this(Type.object);
325         via.map = value;
326     }
327 
328 
329     static UniNode emptyObject() @property
330     {
331         return UniNode(cast(UniNode[string])null);
332     }
333 
334 
335     static UniNode emptyArray() @property
336     {
337         return UniNode(cast(UniNode[])null);
338     }
339 
340 
341     static Type typeId(T)() @property
342     {
343         static if( is(T == typeof(null)) ) return Type.nil;
344         else static if( is(T == bool) ) return Type.boolean;
345         else static if( isFloatingPoint!T ) return Type.floating;
346         else static if( isSignedNumeric!T ) return Type.signed;
347         else static if( isUnsignedNumeric!T ) return Type.unsigned;
348         else static if( isRawData!T ) return Type.raw;
349         else static if( is(T == string) ) return Type.text;
350         else static if( is(T == UniNode[]) ) return Type.array;
351         else static if( is(T == UniNode[string]) ) return Type.object;
352         else static assert(false, "Unsupported UniNode type '"~T.stringof
353                 ~"'. Only bool, long, ulong, double, string, ubyte[], UniNode[] and UniNode[string] are allowed.");
354     }
355 
356 
357     inout(T) get(T)() @property inout @trusted
358     {
359         static if (is(T == string) || isRawData!T)
360             checkType!(T, ubyte[], typeof(null))();
361         else static if(isNumeric!T && (isSigned!T || isUnsigned!T) && !isFloatingPoint!T)
362             checkType!(long, ulong)();
363         else
364             checkType!T();
365 
366         static if (is(T == bool)) return via.boolean;
367         else static if (is(T == double)) return via.floating;
368         else static if (is(T == float)) return cast(T)via.floating;
369         else static if (isSigned!T) return cast(T)via.integer;
370         else static if (isUnsigned!T) return cast(T)via.uinteger;
371         else static if (is(T == string))
372         {
373             if (type == Type.nil)
374                 return "";
375             else
376                 return cast(T)via.raw;
377         }
378         else static if (isRawData!T)
379         {
380             if (type == Type.nil)
381             {
382                 T ret;
383                 return cast(inout(T))ret;
384             }
385             static if (isStaticArray!T)
386                 return cast(inout(T))via.raw[0..T.length];
387             else
388                 return cast(inout(T))via.raw;
389         }
390         else static if (is(T == UniNode[])) return via.array;
391         else static if (is(T == UniNode[string])) return via.map;
392     }
393 
394 
395     ref inout(UniNode) opIndex(size_t idx) inout
396     {
397         checkType!(UniNode[])();
398         return via.array[idx];
399     }
400 
401 
402     ref UniNode opIndex(string key)
403     {
404         checkType!(UniNode[string])();
405         if (auto pv = key in via.map)
406             return *pv;
407 
408         via.map[key] = UniNode();
409         return via.map[key];
410     }
411 
412 
413     void appendArrayElement(UniNode element)
414     {
415         enforceUniNode(type == Type.array, "'appendArrayElement' only allowed for array types, not "
416                 ~.to!string(type)~".");
417         via.array ~= element;
418     }
419 
420 
421     string toString()
422     {
423         auto buff = appender!string;
424 
425         void fun(ref UniNode node)
426         {
427             switch (node.type)
428             {
429                 case Type.nil:
430                     buff.put("nil");
431                     break;
432                 case Type.boolean:
433                     buff.put("bool("~node.get!bool.to!string~")");
434                     break;
435                 case Type.unsigned:
436                     buff.put("unsigned("~node.get!ulong.to!string~")");
437                     break;
438                 case Type.signed:
439                     buff.put("signed("~node.get!long.to!string~")");
440                     break;
441                 case Type.floating:
442                     buff.put("floating("~node.get!double.to!string~")");
443                     break;
444                 case Type.text:
445                     buff.put("text("~node.get!string.to!string~")");
446                     break;
447                 case Type.raw:
448                     buff.put("raw("~node.get!(ubyte[]).to!string~")");
449                     break;
450                 case Type.object:
451                 {
452                     buff.put("{");
453                     size_t len = node.via.map.length;
454                     size_t count;
455                     foreach (k, v; node.via.map)
456                     {
457                         count++;
458                         buff.put(k ~ ":");
459                         fun(v);
460                         if (count < len)
461                             buff.put(", ");
462                     }
463                     buff.put("}");
464                     break;
465                 }
466                 case Type.array:
467                 {
468                     buff.put("[");
469                     size_t len = node.via.array.length;
470                     foreach (i, v; node.via.array)
471                     {
472                         fun(v);
473                         if (i < len)
474                             buff.put(", ");
475                     }
476                     buff.put("]");
477                     break;
478                 }
479                 default:
480                     buff.put("undefined");
481                     break;
482             }
483         }
484 
485         fun(this);
486         return buff.data;
487     }
488 
489 
490 private :
491 
492     void checkType(TYPES...)(string op = null) const
493     {
494         bool matched = false;
495         foreach (T; TYPES)
496         {
497             if (type == typeId!T)
498                 matched = true;
499         }
500 
501         if (matched)
502             return;
503 
504         string expected;
505         static if (TYPES.length == 1)
506             expected = typeId!(TYPES[0]).to!string;
507         else
508         {
509             foreach (T; TYPES)
510             {
511                 if (expected.length > 0)
512                     expected ~= ", ";
513                 expected ~= typeId!T.to!string;
514             }
515         }
516 
517         string name = "UniNode of type " ~ type.to!string;
518         if (!op.length)
519             throw new UniNodeException("Got %s, expected %s.".fmt(name, expected));
520         else
521             throw new UniNodeException("Got %s, expected %s for %s.".fmt(name, expected, op));
522     }
523 }
524 
525 
526 
527 struct UniNodeSerializer
528 {
529     enum isSupportedValueType(T) = is(T == typeof(null))
530                 || isFloatingPoint!T
531                 || isBoolean!T
532                 || isRawData!T
533                 || (isNumeric!T && (isSigned!T || isUnsigned!T))
534                 || is(T == string)
535                 || is(T == UniNode);
536 
537     private
538     {
539         UniNode _current;
540         UniNode[] _stack;
541     }
542 
543 
544     @disable this(this);
545 
546 
547     this(UniNode data) @safe
548     {
549         _current = data;
550     }
551 
552 
553     // serialization
554     UniNode getSerializedResult()
555     {
556         return _current;
557     }
558 
559 
560     void beginWriteDictionary(TypeTraits)()
561     {
562         _stack ~= UniNode.emptyObject();
563     }
564 
565 
566     void endWriteDictionary(TypeTraits)()
567     {
568         _current = _stack[$-1];
569         _stack.length--;
570     }
571 
572 
573     void beginWriteDictionaryEntry(ElementTypeTraits)(string name) {}
574 
575 
576     void endWriteDictionaryEntry(ElementTypeTraits)(string name)
577     {
578         _stack[$-1][name] = _current;
579     }
580 
581 
582     void beginWriteArray(TypeTraits)(size_t length)
583     {
584         _stack ~= UniNode.emptyArray();
585     }
586 
587 
588     void endWriteArray(TypeTraits)()
589     {
590         _current = _stack[$-1];
591         _stack.length--;
592     }
593 
594 
595     void beginWriteArrayEntry(ElementTypeTraits)(size_t index) {}
596 
597 
598     void endWriteArrayEntry(ElementTypeTraits)(size_t index)
599     {
600         _stack[$-1].appendArrayElement(_current);
601     }
602 
603 
604     void writeValue(TypeTraits, T)(T value) if (!is(T == UniNode))
605     {
606         _current = UniNode(value);
607     }
608 
609 
610     // deserialization
611     void readDictionary(TypeTraits)(scope void delegate(string) entry_callback)
612     {
613         enforceUniNode(_current.type == UniNode.Type.object, "Expected UniNode object");
614         auto old = _current;
615         foreach (string key, value; _current.get!(UniNode[string]))
616         {
617             _current = value;
618             entry_callback(key);
619         }
620         _current = old;
621     }
622 
623 
624     void beginReadDictionaryEntry(ElementTypeTraits)(string) {}
625 
626 
627     void endReadDictionaryEntry(ElementTypeTraits)(string) {}
628 
629 
630     void readArray(TypeTraits)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback)
631     {
632         enforceUniNode(_current.type == UniNode.Type.array, "Expected UniNode array");
633         auto old = _current;
634         UniNode[] arr = old.get!(UniNode[]);
635         size_callback(arr.length);
636         foreach (ent; arr)
637         {
638             _current = ent;
639             entry_callback();
640         }
641         _current = old;
642     }
643 
644 
645     void beginReadArrayEntry(ElementTypeTraits)(size_t index) {}
646 
647 
648     void endReadArrayEntry(ElementTypeTraits)(size_t index) {}
649 
650 
651     T readValue(TypeTraits, T)() @safe
652     {
653         static if (is(T == UniNode))
654             return _current;
655         else static if (is(T == float) || is(T == double))
656         {
657             switch (_current.type)
658             {
659                 default:
660                     return cast(T)_current.get!long;
661                 case UniNode.Type.nil:
662                     goto case;
663                 case UniNode.Type.floating:
664                     return cast(T)_current.get!double;
665             }
666         }
667         else
668             return _current.get!T();
669     }
670 
671 
672     bool tryReadNull(TypeTraits)()
673     {
674         return _current.type == UniNode.Type.nil;
675     }
676 }
677 
678 
679 unittest
680 {
681     struct FD
682     {
683         int a;
684         ubyte[3] vector;
685     }
686 
687     FD fd = FD(1, [1, 2, 3]);
688     auto data = marshalObject(fd);
689     assert(data.type == UniNode.Type.object);
690 }
691 
692 
693 class UniNodeException : Exception
694 {
695     this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
696     {
697         super(msg, file, line, next);
698     }
699 }
700 
701 
702 alias enforceUniNode = enforceEx!UniNodeException;