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 }