1 // Written in the D programming language.
2 
3 /**
4  * MessagePack serializer and deserializer implementation.
5  *
6  * MessagePack is a binary-based serialization specification.
7  *
8  * Example:
9  * -----
10  * auto data = tuple("MessagePack!", [1, 2], true);
11  *
12  * auto serialized = pack(data);
13  *
14  * // ...
15  *
16  * typeof(data) deserialized;
17  *
18  * unpack(serialized, deserialized);
19  *
20  * assert(data == deserialized);
21  * -----
22  *
23  * See_Also:
24  *  $(LINK2 http://msgpack.org/, The MessagePack Project)$(BR)
25  *  $(LINK2 https://github.com/msgpack/msgpack/blob/master/spec.md, MessagePack data format)
26  *
27  * Copyright: Copyright Masahiro Nakagawa 2010-.
28  * License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
29  * Authors:   Masahiro Nakagawa
30  */
31 module msgpack;
32 
33 public:
34 
35 import msgpack.common;
36 import msgpack.attribute;
37 import msgpack.buffer;
38 import msgpack.exception;
39 import msgpack.packer;
40 import msgpack.unpacker;
41 import msgpack.streaming_unpacker;
42 import msgpack.register;
43 import msgpack.value;
44 
45 
46 @trusted:
47 
48 
49 /**
50  * Serializes $(D_PARAM args).
51  *
52  * Assumes single object if the length of $(D_PARAM args) == 1,
53  * otherwise array object.
54  *
55  * Params:
56  *  args = the contents to serialize.
57  *
58  * Returns:
59  *  a serialized data.
60  */
61 ubyte[] pack(bool withFieldName = false, Args...)(in Args args)
62 {
63     auto packer = Packer(withFieldName);
64 
65     static if (Args.length == 1)
66         packer.pack(args[0]);
67     else
68         packer.packArray(args);
69 
70     return packer.stream.data;
71 }
72 
73 
74 /**
75  * Deserializes $(D_PARAM buffer) using stream deserializer.
76  *
77  * Params:
78  *  buffer = the buffer to deserialize.
79  *
80  * Returns:
81  *  a $(D Unpacked) contains deserialized object.
82  *
83  * Throws:
84  *  UnpackException if deserialization doesn't succeed.
85  */
86 Unpacked unpack(in ubyte[] buffer)
87 {
88     auto unpacker = StreamingUnpacker(buffer);
89 
90     if (!unpacker.execute())
91         throw new UnpackException("Deserialization failure");
92 
93     return unpacker.unpacked;
94 }
95 
96 
97 /**
98  * Deserializes $(D_PARAM buffer) using direct-conversion deserializer.
99  *
100  * Assumes single object if the length of $(D_PARAM args) == 1,
101  * otherwise array object.
102  *
103  * Params:
104  *  buffer = the buffer to deserialize.
105  *  args   = the references of values to assign.
106  */
107 void unpack(bool withFieldName = false, Args...)(in ubyte[] buffer, ref Args args)
108 {
109     auto unpacker = Unpacker(buffer, buffer.length, withFieldName);
110 
111     static if (Args.length == 1)
112         unpacker.unpack(args[0]);
113     else
114         unpacker.unpackArray(args);
115 }
116 
117 
118 /**
119  * Return value version
120  */
121 Type unpack(Type, bool withFieldName = false)(in ubyte[] buffer)
122 {
123     auto unpacker = Unpacker(buffer, buffer.length, withFieldName);
124 
125     Type result;
126     unpacker.unpack(result);
127     return result;
128 }
129 
130 
131 unittest
132 {
133     auto serialized = pack(false);
134 
135     assert(serialized[0] == Format.FALSE);
136 
137     auto deserialized = unpack(pack(1, true, "Foo"));
138 
139     assert(deserialized.type == Value.Type.array);
140     assert(deserialized.via.array[0].type == Value.Type.unsigned);
141     assert(deserialized.via.array[1].type == Value.Type.boolean);
142     assert(deserialized.via.array[2].type == Value.Type.raw);
143 }
144 
145 
146 unittest
147 {
148     import std.typecons;
149 
150     { // stream
151         auto result = unpack(pack(false));
152 
153         assert(result.via.boolean == false);
154     }
155     { // direct conversion
156         Tuple!(uint, string) result;
157         Tuple!(uint, string) test = tuple(1, "Hi!");
158 
159         unpack(pack(test), result);
160         assert(result == test);
161 
162         test.field[0] = 2;
163         test.field[1] = "Hey!";
164         unpack(pack(test.field[0], test.field[1]), result.field[0], result.field[1]);
165         assert(result == test);
166     }
167     { // return value direct conversion
168         Tuple!(uint, string) test = tuple(1, "Hi!");
169 
170         auto data = pack(test);
171         assert(data.unpack!(Tuple!(uint, string)) == test);
172     }
173     { // serialize object as a Map
174         static class C
175         {
176             int num;
177 
178             this(int num) { this.num = num; }
179         }
180 
181         auto test = new C(10);
182         auto result = new C(100);
183 
184         unpack!(true)(pack!(true)(test), result);
185         assert(result.num == 10, "Unpacking with field names failed");
186     }
187 }
188 
189 
190 unittest
191 {
192     import std.typetuple;
193 
194     // unittest for https://github.com/msgpack/msgpack-d/issues/8
195     foreach (Type; TypeTuple!(byte, short, int, long)) {
196         foreach (i; [-33, -20, -1, 0, 1, 20, 33]) {
197             Type a = cast(Type)i;
198             Type b;
199             unpack(pack(a), b);
200             assert(a == b);
201         }
202     }
203 }
204 
205 
206 unittest
207 {
208     import std.typetuple;
209     
210     // char types
211     foreach (Type; TypeTuple!(char, wchar, dchar)) {
212         foreach (i; [Type.init, Type.min, Type.max, cast(Type)'j']) {
213             Type a = i;
214             Type b;
215             unpack(pack(a), b);
216             assert(a == b);
217         }
218     }
219 }
220 
221 unittest
222 {
223     // ext type
224     auto result = unpack(pack(ExtValue(7, [1,2,3,4])));
225     assert(result == ExtValue(7, [1,2,3,4]));
226 }
227 
228 
229 /**
230  * Handy helper for creating MessagePackable object.
231  *
232  * toMsgpack / fromMsgpack are special methods for serialization / deserialization.
233  * This template provides those methods to struct/class.
234  *
235  * Example:
236  * -----
237  * struct S
238  * {
239  *     int num; string str;
240  *
241  *     // http://d.puremagic.com/issues/show_bug.cgi?id = 1099
242  *     mixin MessagePackable;  // all members
243  *     // mixin MessagePackable!("num");  // num only
244  * }
245  * -----
246  *
247  * Defines those methods manually if you treat complex data-structure.
248  */
249 mixin template MessagePackable(Members...)
250 {
251     static if (Members.length == 0) {
252         /**
253          * Serializes members using $(D_PARAM packer).
254          *
255          * Params:
256          *  packer = the serializer to pack.
257          */
258         void toMsgpack(Packer)(ref Packer packer, bool withFieldName = false) const
259         {
260             if (withFieldName) {
261                 packer.beginMap(this.tupleof.length);
262                 foreach (i, member; this.tupleof) {
263                     packer.pack(getFieldName!(typeof(this), i));
264                     packer.pack(member);
265                 }
266             } else {
267                 packer.beginArray(this.tupleof.length);
268                 foreach (member; this.tupleof)
269                     packer.pack(member);
270             }
271         }
272 
273 
274         /**
275          * Deserializes $(D MessagePack) object to members using Value.
276          *
277          * Params:
278          *  value = the MessagePack value to unpack.
279          *
280          * Throws:
281          *  MessagePackException if $(D_PARAM value) is not an Array type.
282          */
283         void fromMsgpack(Value value)
284         {
285             // enables if std.contracts.enforce is moved to object_.d
286             // enforceEx!MessagePackException(value.type == Value.Type.array, "Value must be Array type");
287             if (value.type != Value.Type.array)
288                 throw new MessagePackException("Value must be an Array type");
289             if (value.via.array.length != this.tupleof.length)
290                 throw new MessagePackException("The size of deserialized value is mismatched");
291 
292             foreach (i, member; this.tupleof)
293                 this.tupleof[i] = value.via.array[i].as!(typeof(member));
294         }
295 
296 
297         /**
298          * Deserializes $(D MessagePack) object to members using direct-conversion deserializer.
299          *
300          * Params:
301          *  value = the reference to direct-conversion deserializer.
302          *
303          * Throws:
304          *  MessagePackException if the size of deserialized value is mismatched.
305          */
306         void fromMsgpack(ref Unpacker unpacker)
307         {
308             auto length = unpacker.beginArray();
309             if (length != this.tupleof.length)
310                 throw new MessagePackException("The size of deserialized value is mismatched");
311 
312             foreach (i, member; this.tupleof)
313                 unpacker.unpack(this.tupleof[i]);
314         }
315     } else {
316         /**
317          * Member selecting version of toMsgpack.
318          */
319         void toMsgpack(Packer)(ref Packer packer, bool withFieldName = false) const
320         {
321             if (withFieldName) {
322                 packer.beginMap(Members.length);
323                 foreach (member; Members) {
324                     packer.pack(member);
325                     packer.pack(mixin(member));
326                 }
327             } else {
328                 packer.beginArray(Members.length);
329                 foreach (member; Members)
330                     packer.pack(mixin(member));
331             }
332         }
333 
334 
335         /**
336          * Member selecting version of fromMsgpack for Value.
337          */
338         void fromMsgpack(Value value)
339         {
340             if (value.type != Value.Type.array)
341                 throw new MessagePackException("Value must be an Array type");
342             if (value.via.array.length != Members.length)
343                 throw new MessagePackException("The size of deserialized value is mismatched");
344 
345             foreach (i, member; Members)
346                 mixin(member ~ "= value.via.array[i].as!(typeof(" ~ member ~ "));");
347         }
348 
349 
350         /**
351          * Member selecting version of fromMsgpack for direct-converion deserializer.
352          */
353         void fromMsgpack(ref Unpacker unpacker)
354         {
355             auto length = unpacker.beginArray();
356             if (length != Members.length)
357                 throw new MessagePackException("The size of deserialized value is mismatched");
358 
359             foreach (member; Members)
360                 unpacker.unpack(mixin(member));
361         }
362     }
363 }
364 
365 
366 unittest
367 {
368     { // all members
369         /*
370          * Comment out because "src/msgpack.d(4048): Error: struct msgpack.__unittest16.S no size yet for forward reference" occurs
371          */
372         static struct S
373         {
374             uint num; string str;
375             mixin MessagePackable;
376         }
377 
378         mixin DefinePacker;
379 
380         S orig = S(10, "Hi!"); orig.toMsgpack(packer);
381 
382         { // stream
383             auto unpacker = StreamingUnpacker(packer.stream.data); unpacker.execute();
384 
385             S result; result.fromMsgpack(unpacker.unpacked);
386 
387             assert(result.num == 10);
388             assert(result.str == "Hi!");
389         }
390         { // direct conversion
391             auto unpacker = Unpacker(packer.stream.data);
392 
393             S result; unpacker.unpack(result);
394 
395             assert(result.num == 10);
396             assert(result.str == "Hi!");
397         }
398     }
399     { // member select
400         static class C
401         {
402             uint num; string str;
403 
404             this() {}
405             this(uint n, string s) { num = n; str = s; }
406 
407             mixin MessagePackable!("num");
408         }
409 
410         mixin DefinePacker;
411 
412         C orig = new C(10, "Hi!"); orig.toMsgpack(packer);
413 
414         { // stream
415             auto unpacker = StreamingUnpacker(packer.stream.data); unpacker.execute();
416 
417             C result = new C; result.fromMsgpack(unpacker.unpacked);
418 
419             assert(result.num == 10);
420         }
421         { // direct conversion
422             auto unpacker = Unpacker(packer.stream.data);
423 
424             C result; unpacker.unpack(result);
425 
426             assert(result.num == 10);
427         }
428     }
429 }