1 /** 2 Generic serialization framework. 3 4 This module provides general means for implementing (de-)serialization with 5 a standardized behavior. 6 7 Supported_types: 8 The following rules are applied in order when serializing or 9 deserializing a certain type: 10 11 $(OL 12 $(LI An `enum` type is serialized as its raw value, except if 13 `@byName` is used, in which case the name of the enum value 14 is serialized.) 15 $(LI Any type that is specifically supported by the serializer 16 is directly serialized. For example, the BSON serializer 17 supports `BsonObjectID` directly.) 18 $(LI Arrays and tuples (`std.typecons.Tuple`) are serialized 19 using the array serialization functions where each element is 20 serialized again according to these rules.) 21 $(LI Associative arrays are serialized similar to arrays. The key 22 type of the AA must satisfy the `isStringSerializable` trait 23 and will always be serialized as a string.) 24 $(LI Any `Nullable!T` will be serialized as either `null`, or 25 as the contained value (subject to these rules again).) 26 $(LI Any `Typedef!T` will be serialized as if it were just `T`.) 27 $(LI Any `BitFlags!T` value will be serialized as `T[]`) 28 $(LI Types satisfying the `isPolicySerializable` trait for the 29 supplied `Policy` will be serialized as the value returned 30 by the policy `toRepresentation` function (again subject to 31 these rules).) 32 $(LI Types satisfying the `isCustomSerializable` trait will be 33 serialized as the value returned by their `toRepresentation` 34 method (again subject to these rules).) 35 $(LI Types satisfying the `isISOExtStringSerializable` trait will be 36 serialized as a string, as returned by their `toISOExtString` 37 method. This causes types such as `SysTime` to be serialized 38 as strings.) 39 $(LI Types satisfying the `isStringSinkSerializable` trait will be 40 serialized as a string using the `toString(sink)` method. `sink` 41 can either be a delegate that takes a `char` array argument, or 42 an output range of `char`.) 43 $(LI Types satisfying the `isStringSerializable` trait will be 44 serialized as a string, as returned by their `toString` 45 method.) 46 $(LI Struct and class types by default will be serialized as 47 associative arrays, where the key is the name of the 48 corresponding field (can be overridden using the `@name` 49 attribute). If the struct/class is annotated with `@asArray`, 50 it will instead be serialized as a flat array of values in the 51 order of declaration. Null class references will be serialized 52 as `null`.) 53 $(LI Pointer types will be serialized as either `null`, or as 54 the value they point to.) 55 $(LI Built-in integers and floating point values, as well as 56 boolean values will be converted to strings, if the serializer 57 doesn't support them directly.) 58 ) 59 60 Note that no aliasing detection is performed, so that pointers, class 61 references and arrays referencing the same memory will be serialized 62 as multiple copies. When in turn deserializing the data, they will also 63 end up as separate copies in memory. 64 65 Field_names: 66 By default, the field name of the serialized D type (for `struct` and 67 `class` aggregates) is represented as-is in the serialized result. To 68 circumvent name clashes with D's keywords, a single trailing underscore of 69 any field name is stipped, so that a field name of `version_` results in 70 just `"version"` as the serialized value. Names can also be freely 71 customized using the `@name` annotation. 72 73 Associative array keys are always represented using their direct string 74 representation. 75 76 Serializer_implementation: 77 Serializers are implemented in terms of a struct with template methods that 78 get called by the serialization framework: 79 80 --- 81 struct ExampleSerializer { 82 enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null)); 83 84 // serialization 85 auto getSerializedResult(); 86 void beginWriteDocument(TypeTraits)(); 87 void endWriteDocument(TypeTraits)(); 88 void beginWriteDictionary(TypeTraits)(size_t length); [OR] void beginWriteDictionary(TypeTraits)(); 89 void endWriteDictionary(TypeTraits)(); 90 void beginWriteDictionaryEntry(ElementTypeTraits)(string name); 91 void endWriteDictionaryEntry(ElementTypeTraits)(string name); 92 void beginWriteArray(TypeTraits)(size_t length); 93 void endWriteArray(TypeTraits)(); 94 void beginWriteArrayEntry(ElementTypeTraits)(size_t index); 95 void endWriteArrayEntry(ElementTypeTraits)(size_t index); 96 void writeValue(TypeTraits, T)(T value); 97 98 // deserialization 99 100 void readDictionary(TypeTraits)(scope void delegate(string) entry_callback); 101 void beginReadDictionaryEntry(ElementTypeTraits)(string); 102 void endReadDictionaryEntry(ElementTypeTraits)(string); 103 void readArray(TypeTraits)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback); 104 void beginReadArrayEntry(ElementTypeTraits)(size_t index); 105 void endReadArrayEntry(ElementTypeTraits)(size_t index); 106 T readValue(TypeTraits, T)(); 107 bool tryReadNull(TypeTraits)(); 108 109 // skipValue() is optional. It will be called by the entry_callback in readDictionary 110 // whenever the key passed to the entry_callback cannot be found. 111 void skipValue(); 112 } 113 --- 114 115 The `TypeTraits` type passed to the individual methods has the following members: 116 $(UL 117 $(LI `Type`: The original type of the field to serialize) 118 $(LI `Attributes`: User defined attributes attached to the field) 119 $(LI `Policy`: An alias to the policy used for the serialization process) 120 ) 121 122 `ElementTypeTraits` have the following additional members: 123 $(UL 124 $(LI `ContainerType`: The original type of the enclosing container type) 125 $(LI `ContainerAttributes`: User defined attributes attached to the enclosing container) 126 ) 127 128 Copyright: © 2013-2016 rejectedsoftware e.K. 129 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 130 Authors: Sönke Ludwig 131 */ 132 module dub.internal.vibecompat.data.serialization; 133 134 version (Have_vibe_serialization) 135 public import vibe.data.serialization; 136 else: 137 138 import dub.internal.vibecompat.data.traits; 139 import dub.internal.vibecompat.data.uda; 140 141 import std.array : Appender, appender; 142 import std.conv : ConvException, to; 143 import std.exception : enforce; 144 import std.range.primitives : ElementType, isInputRange; 145 import std.traits; 146 import std.meta; 147 148 149 /** 150 Serializes a value with the given serializer. 151 152 The serializer must have a value result for the first form 153 to work. Otherwise, use the range based form. 154 155 See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` 156 */ 157 auto serialize(Serializer, T, ARGS...)(auto ref T value, ARGS args) 158 { 159 auto serializer = Serializer(args); 160 serialize(serializer, value); 161 return serializer.getSerializedResult(); 162 } 163 /// ditto 164 void serialize(Serializer, T)(ref Serializer serializer, auto ref T value) 165 { 166 serializeWithPolicy!(Serializer, DefaultPolicy)(serializer, value); 167 } 168 169 /** Note that there is a convenience function `vibe.data.json.serializeToJson` 170 that can be used instead of manually invoking `serialize`. 171 */ 172 unittest { 173 import dub.internal.vibecompat.data.json; 174 175 struct Test { 176 int value; 177 string text; 178 } 179 180 Test test; 181 test.value = 12; 182 test.text = "Hello"; 183 184 Json serialized = serialize!JsonSerializer(test); 185 assert(serialized["value"].get!int == 12); 186 assert(serialized["text"].get!string == "Hello"); 187 } 188 189 unittest { 190 import dub.internal.vibecompat.data.json; 191 192 // Make sure that immutable(char[]) works just like string 193 // (i.e., immutable(char)[]). 194 immutable key = "answer"; 195 auto ints = [key: 42]; 196 auto serialized = serialize!JsonSerializer(ints); 197 assert(serialized[key].get!int == 42); 198 } 199 200 /** 201 Serializes a value with the given serializer, representing values according to `Policy` when possible. 202 203 The serializer must have a value result for the first form 204 to work. Otherwise, use the range based form. 205 206 See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` 207 */ 208 auto serializeWithPolicy(Serializer, alias Policy, T, ARGS...)(auto ref T value, ARGS args) 209 { 210 auto serializer = Serializer(args); 211 serializeWithPolicy!(Serializer, Policy)(serializer, value); 212 return serializer.getSerializedResult(); 213 } 214 /// ditto 215 void serializeWithPolicy(Serializer, alias Policy, T)(ref Serializer serializer, auto ref T value) 216 { 217 static if (is(typeof(serializer.beginWriteDocument!T()))) 218 serializer.beginWriteDocument!T(); 219 serializeValueImpl!(Serializer, Policy).serializeValue!T(serializer, value); 220 static if (is(typeof(serializer.endWriteDocument!T()))) 221 serializer.endWriteDocument!T(); 222 } 223 /// 224 version (unittest) 225 { 226 } 227 228 /// 229 unittest { 230 import dub.internal.vibecompat.data.json; 231 import std.meta : AliasSeq; 232 233 template SizePol(T) 234 if (__traits(allMembers, T) == AliasSeq!("x", "y")) 235 { 236 import std.conv; 237 import std.array; 238 239 static string toRepresentation(T value) @safe { 240 return to!string(value.x) ~ "x" ~ to!string(value.y); 241 } 242 243 static T fromRepresentation(string value) { 244 string[] fields = value.split('x'); 245 alias fieldT = typeof(T.x); 246 auto x = to!fieldT(fields[0]); 247 auto y = to!fieldT(fields[1]); 248 return T(x, y); 249 } 250 } 251 252 static struct SizeI { 253 int x; 254 int y; 255 } 256 SizeI sizeI = SizeI(1,2); 257 Json serializedI = serializeWithPolicy!(JsonSerializer, SizePol)(sizeI); 258 assert(serializedI.get!string == "1x2"); 259 260 static struct SizeF { 261 float x; 262 float y; 263 } 264 SizeF sizeF = SizeF(0.1f,0.2f); 265 Json serializedF = serializeWithPolicy!(JsonSerializer, SizePol)(sizeF); 266 assert(serializedF.get!string == "0.1x0.2"); 267 } 268 269 270 /** 271 Deserializes and returns a serialized value. 272 273 serialized_data can be either an input range or a value containing 274 the serialized data, depending on the type of serializer used. 275 276 See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` 277 */ 278 T deserialize(Serializer, T, ARGS...)(ARGS args) 279 { 280 return deserializeWithPolicy!(Serializer, DefaultPolicy, T)(args); 281 } 282 283 /** Note that there is a convenience function `vibe.data.json.deserializeJson` 284 that can be used instead of manually invoking `deserialize`. 285 */ 286 unittest { 287 import dub.internal.vibecompat.data.json; 288 289 struct Test { 290 int value; 291 string text; 292 } 293 294 Json serialized = Json.emptyObject; 295 serialized["value"] = 12; 296 serialized["text"] = "Hello"; 297 298 Test test = deserialize!(JsonSerializer, Test)(serialized); 299 assert(test.value == 12); 300 assert(test.text == "Hello"); 301 } 302 303 /** 304 Deserializes and returns a serialized value, interpreting values according to `Policy` when possible. 305 306 serialized_data can be either an input range or a value containing 307 the serialized data, depending on the type of serializer used. 308 309 See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` 310 */ 311 T deserializeWithPolicy(Serializer, alias Policy, T, ARGS...)(ARGS args) 312 { 313 auto deserializer = Serializer(args); 314 return deserializeValueImpl!(Serializer, Policy).deserializeValue!T(deserializer); 315 } 316 317 /// 318 unittest { 319 import dub.internal.vibecompat.data.json; 320 import std.meta : AliasSeq; 321 322 template SizePol(T) 323 if (__traits(allMembers, T) == AliasSeq!("x", "y")) 324 { 325 import std.conv; 326 import std.array; 327 328 static string toRepresentation(T value) 329 @safe { 330 return to!string(value.x) ~ "x" ~ to!string(value.y); 331 } 332 333 static T fromRepresentation(string value) 334 @safe { 335 string[] fields = value.split('x'); 336 alias fieldT = typeof(T.x); 337 auto x = to!fieldT(fields[0]); 338 auto y = to!fieldT(fields[1]); 339 return T(x, y); 340 } 341 } 342 343 static struct SizeI { 344 int x; 345 int y; 346 } 347 348 Json serializedI = "1x2"; 349 SizeI sizeI = deserializeWithPolicy!(JsonSerializer, SizePol, SizeI)(serializedI); 350 assert(sizeI.x == 1); 351 assert(sizeI.y == 2); 352 353 static struct SizeF { 354 float x; 355 float y; 356 } 357 Json serializedF = "0.1x0.2"; 358 SizeF sizeF = deserializeWithPolicy!(JsonSerializer, SizePol, SizeF)(serializedF); 359 assert(sizeF.x == 0.1f); 360 assert(sizeF.y == 0.2f); 361 } 362 363 private template serializeValueImpl(Serializer, alias Policy) { 364 alias _Policy = Policy; 365 static assert(Serializer.isSupportedValueType!string, "All serializers must support string values."); 366 static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values."); 367 368 // work around https://issues.dlang.org/show_bug.cgi?id=16528 369 static if (isSafeSerializer!Serializer) { 370 void serializeValue(T, ATTRIBUTES...)(ref Serializer ser, auto ref T value) @safe { serializeValueDeduced!(T, ATTRIBUTES)(ser, value); } 371 } else { 372 void serializeValue(T, ATTRIBUTES...)(ref Serializer ser, auto ref T value) { serializeValueDeduced!(T, ATTRIBUTES)(ser, value); } 373 } 374 375 private void serializeValueDeduced(T, FIELD_ATTRIBUTES...)(ref Serializer ser, auto ref T value) 376 { 377 import std.typecons : BitFlags, Nullable, Tuple, Typedef, TypedefType, tuple; 378 379 alias TU = Unqual!T; 380 381 alias ATTRIBUTES = AliasSeq!(FIELD_ATTRIBUTES, TypeAttributes!T); 382 383 alias Traits = .Traits!(TU, _Policy, ATTRIBUTES); 384 385 static if (isPolicySerializable!(Policy, TU)) { 386 alias CustomType = typeof(Policy!TU.toRepresentation(TU.init)); 387 ser.serializeValue!(CustomType, ATTRIBUTES)(Policy!TU.toRepresentation(value)); 388 } else static if (is(TU == enum)) { 389 static if (hasPolicyAttributeL!(ByNameAttribute, Policy, ATTRIBUTES)) { 390 ser.serializeValue!(string)(value.to!string()); 391 } else { 392 ser.serializeValue!(OriginalType!TU)(cast(OriginalType!TU)value); 393 } 394 } else static if (Serializer.isSupportedValueType!TU) { 395 static if (is(TU == typeof(null))) ser.writeValue!Traits(null); 396 else ser.writeValue!(Traits)(value); 397 } else static if (/*isInstanceOf!(Tuple, TU)*/is(T == Tuple!TPS, TPS...)) { 398 import std.algorithm.searching: all; 399 static if (all!"!a.empty"([TU.fieldNames]) && 400 !hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) { 401 static if (__traits(compiles, ser.beginWriteDictionary!TU(0))) { 402 auto nfields = value.length; 403 ser.beginWriteDictionary!Traits(nfields); 404 } else { 405 ser.beginWriteDictionary!Traits(); 406 } 407 foreach (i, _; T.Types) { 408 alias TV = typeof(value[i]); 409 alias STraits = SubTraits!(Traits, TV); 410 ser.beginWriteDictionaryEntry!STraits(underscoreStrip(TU.fieldNames[i])); 411 ser.serializeValue!(TV, ATTRIBUTES)(value[i]); 412 ser.endWriteDictionaryEntry!STraits(underscoreStrip(TU.fieldNames[i])); 413 } 414 static if (__traits(compiles, ser.endWriteDictionary!TU(0))) { 415 ser.endWriteDictionary!Traits(nfields); 416 } else { 417 ser.endWriteDictionary!Traits(); 418 } 419 } else static if (TU.Types.length == 1) { 420 ser.serializeValue!(typeof(value[0]), ATTRIBUTES)(value[0]); 421 } else { 422 ser.beginWriteArray!Traits(value.length); 423 foreach (i, _; T.Types) { 424 alias TV = typeof(value[i]); 425 alias STraits = SubTraits!(Traits, TV); 426 ser.beginWriteArrayEntry!STraits(i); 427 ser.serializeValue!(TV, ATTRIBUTES)(value[i]); 428 ser.endWriteArrayEntry!STraits(i); 429 } 430 ser.endWriteArray!Traits(); 431 } 432 } else static if (isArray!TU) { 433 alias TV = typeof(value[0]); 434 alias STraits = SubTraits!(Traits, TV); 435 ser.beginWriteArray!Traits(value.length); 436 foreach (i, ref el; value) { 437 ser.beginWriteArrayEntry!STraits(i); 438 ser.serializeValue!(TV, ATTRIBUTES)(el); 439 ser.endWriteArrayEntry!STraits(i); 440 } 441 ser.endWriteArray!Traits(); 442 } else static if (isAssociativeArray!TU) { 443 alias TK = KeyType!TU; 444 alias TV = ValueType!TU; 445 alias STraits = SubTraits!(Traits, TV); 446 447 static if (__traits(compiles, ser.beginWriteDictionary!TU(0))) { 448 auto nfields = value.length; 449 ser.beginWriteDictionary!Traits(nfields); 450 } else { 451 ser.beginWriteDictionary!Traits(); 452 } 453 foreach (key, ref el; value) { 454 string keyname; 455 static if (is(TK : string)) keyname = key; 456 else static if (is(TK : real) || is(TK : long) || is(TK == enum)) keyname = key.to!string; 457 else static if (isStringSerializable!TK) keyname = key.toString(); 458 else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods."); 459 ser.beginWriteDictionaryEntry!STraits(keyname); 460 ser.serializeValue!(TV, ATTRIBUTES)(el); 461 ser.endWriteDictionaryEntry!STraits(keyname); 462 } 463 static if (__traits(compiles, ser.endWriteDictionary!TU(0))) { 464 ser.endWriteDictionary!Traits(nfields); 465 } else { 466 ser.endWriteDictionary!Traits(); 467 } 468 } else static if (/*isInstanceOf!(Nullable, TU)*/is(T == Nullable!TPS, TPS...)) { 469 if (value.isNull()) ser.serializeValue!(typeof(null))(null); 470 else ser.serializeValue!(typeof(value.get()), ATTRIBUTES)(value.get()); 471 } else static if (isInstanceOf!(Typedef, TU)) { 472 ser.serializeValue!(TypedefType!TU, ATTRIBUTES)(cast(TypedefType!TU)value); 473 } else static if (is(TU == BitFlags!E, E)) { 474 alias STraits = SubTraits!(Traits, E); 475 476 size_t cnt = 0; 477 foreach (v; EnumMembers!E) 478 if (value & v) 479 cnt++; 480 481 ser.beginWriteArray!Traits(cnt); 482 cnt = 0; 483 foreach (v; EnumMembers!E) 484 if (value & v) { 485 ser.beginWriteArrayEntry!STraits(cnt); 486 ser.serializeValue!(E, ATTRIBUTES)(v); 487 ser.endWriteArrayEntry!STraits(cnt); 488 cnt++; 489 } 490 ser.endWriteArray!Traits(); 491 } else static if (isCustomSerializable!TU) { 492 alias CustomType = typeof(T.init.toRepresentation()); 493 ser.serializeValue!(CustomType, ATTRIBUTES)(value.toRepresentation()); 494 } else static if (isISOExtStringSerializable!TU) { 495 ser.serializeValue!(string, ATTRIBUTES)(value.toISOExtString()); 496 } else static if (isStringSinkSerializable!TU) { 497 static if (doesSerializerSupportStringSink!Serializer) { 498 ser.writeStringSinkValue!Traits(value); 499 } else { 500 import std.format : formattedWrite; 501 auto app = appender!string; 502 app.formattedWrite("%s", value); 503 ser.serializeValue!(string, ATTRIBUTES)(app.data); 504 } 505 } else static if (isStringSerializable!TU) { 506 ser.serializeValue!(string, ATTRIBUTES)(value.toString()); 507 } else static if (is(TU == struct) || is(TU == class)) { 508 static if (!hasSerializableFields!(TU, Policy)) 509 pragma(msg, "Serializing composite type "~T.stringof~" which has no serializable fields"); 510 static if (is(TU == class)) { 511 if (value is null) { 512 ser.serializeValue!(typeof(null))(null); 513 return; 514 } 515 } 516 static auto safeGetMember(string mname)(ref T val) @safe { 517 static if (__traits(compiles, __traits(getMember, val, mname))) { 518 return __traits(getMember, val, mname); 519 } else { 520 pragma(msg, "Warning: Getter for "~fullyQualifiedName!T~"."~mname~" is not @safe"); 521 return () @trusted { return __traits(getMember, val, mname); } (); 522 } 523 } 524 static if (hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) { 525 enum nfields = getExpandedFieldCount!(TU, SerializableFields!(TU, Policy)); 526 ser.beginWriteArray!Traits(nfields); 527 size_t fcount = 0; 528 foreach (mname; SerializableFields!(TU, Policy)) { 529 alias TMS = AliasSeq!(typeof(__traits(getMember, value, mname))); 530 foreach (j, TM; TMS) { 531 alias TA = AliasSeq!(__traits(getAttributes, AliasSeq!(__traits(getMember, T, mname))[j])); 532 alias STraits = SubTraits!(Traits, TM, TA); 533 ser.beginWriteArrayEntry!STraits(fcount); 534 static if (!isBuiltinTuple!(T, mname)) 535 ser.serializeValue!(TM, TA)(safeGetMember!mname(value)); 536 else 537 ser.serializeValue!(TM, TA)(tuple(__traits(getMember, value, mname))[j]); 538 ser.endWriteArrayEntry!STraits(fcount); 539 fcount++; 540 } 541 } 542 ser.endWriteArray!Traits(); 543 } else { 544 static if (__traits(compiles, ser.beginWriteDictionary!Traits(0))) { 545 auto nfields = getExpandedFieldCount!(TU, SerializableFields!(TU, Policy)); 546 547 foreach (mname; SerializableFields!(TU, Policy)) { 548 static if (!isBuiltinTuple!(T, mname)) { 549 auto vt = safeGetMember!mname(value); 550 static if (is(typeof(vt) : Nullable!NVT, NVT) 551 && hasPolicyAttribute!(EmbedNullableIgnoreNullAttribute, Policy, AliasSeq!(__traits(getMember, T, mname))[0])) { 552 if (vt.isNull) nfields--; 553 } 554 } 555 } 556 557 ser.beginWriteDictionary!Traits(nfields); 558 } else { 559 ser.beginWriteDictionary!Traits(); 560 } 561 foreach (mname; SerializableFields!(TU, Policy)) { 562 alias TM = AliasSeq!(typeof(__traits(getMember, TU, mname))); 563 static if (__traits(getOverloads, T, mname).length > 0) { 564 // combine attributes of overload sets 565 alias getAtts(alias ovl) = __traits(getAttributes, ovl); 566 alias TA = staticMap!(getAtts, __traits(getOverloads, T, mname)); 567 } else { 568 alias TA = AliasSeq!(__traits(getAttributes, AliasSeq!(__traits(getMember, T, mname))[0])); 569 } 570 enum name = getPolicyAttribute!(fullyQualifiedName!TU~"."~mname, NameAttribute, Policy, TA)(NameAttribute!DefaultPolicy(underscoreStrip(mname))).name; 571 static if (!isBuiltinTuple!(T, mname)) { 572 auto vtn = safeGetMember!mname(value); 573 static if (is(typeof(vtn) : Nullable!NVT, NVT) 574 && hasPolicyAttribute!(EmbedNullableIgnoreNullAttribute, Policy, AliasSeq!(__traits(getMember, T, mname))[0])) { 575 if (vtn.isNull) continue; 576 auto vt = vtn.get; 577 } else { 578 auto vt = vtn; 579 } 580 } else { 581 alias TTM = AliasSeq!(typeof(__traits(getMember, value, mname))); 582 auto vt = tuple!TTM(__traits(getMember, value, mname)); 583 } 584 alias STraits = SubTraits!(Traits, typeof(vt), TA); 585 ser.beginWriteDictionaryEntry!STraits(name); 586 ser.serializeValue!(typeof(vt), TA)(vt); 587 ser.endWriteDictionaryEntry!STraits(name); 588 } 589 static if (__traits(compiles, ser.endWriteDictionary!Traits(0))) { 590 ser.endWriteDictionary!Traits(nfields); 591 } else { 592 ser.endWriteDictionary!Traits(); 593 } 594 } 595 } else static if (isPointer!TU) { 596 if (value is null) { 597 ser.writeValue!Traits(null); 598 return; 599 } 600 ser.serializeValue!(PointerTarget!TU)(*value); 601 } else static if (is(TU == bool) || is(TU : real) || is(TU : long)) { 602 ser.serializeValue!(string, ATTRIBUTES)(to!string(value)); 603 } else static assert(false, "Unsupported serialization type: " ~ T.stringof); 604 } 605 } 606 607 /// 608 package template doesSerializerSupportStringSink(SerT) 609 { 610 static struct T1 { void toString(scope void delegate(scope const(char)[])) {} } 611 static struct T2 { void toString(R)(ref R dst) { dst.put('f'); dst.put("foo"); } } 612 613 enum doesSerializerSupportStringSink = 614 is(typeof(SerT.init.writeStringSinkValue!(Traits!(T1, DefaultPolicy))(T1.init))) 615 && is(typeof(SerT.init.writeStringSinkValue!(Traits!(T2, DefaultPolicy))(T2.init))); 616 } 617 618 /// 619 template isStringSinkSerializable(T) 620 { 621 import std.range : nullSink; 622 623 private void sink(S : const(char)[])(scope S s) @safe {} 624 625 enum isStringSinkSerializable = 626 ( 627 is(typeof(T.init.toString((scope str) => sink(str)))) 628 || is(typeof(T.init.toString(nullSink))) 629 ) 630 && is(typeof(T.fromString(string.init)) : T); 631 } 632 633 unittest { 634 import std.array : split; 635 import std.format : formattedWrite; 636 import dub.internal.vibecompat.data.json; 637 638 static struct X(alias hasSink) { 639 private int i; 640 private string s; 641 642 static if (hasSink) { 643 void toString (scope void delegate(scope const(char)[]) @safe dg) @safe 644 { 645 formattedWrite(dg, "%d;%s", this.i, this.s); 646 } 647 } 648 649 string toString () @safe const pure nothrow 650 { 651 return "42;hello"; 652 } 653 654 static X fromString (string s) @safe pure 655 { 656 auto parts = s.split(";"); 657 auto x = X(parts[0].to!int, parts[1]); 658 return x; 659 } 660 } 661 662 static assert(!isStringSinkSerializable!(X!false)); 663 static assert(isStringSinkSerializable!(X!true)); 664 665 // old toString() style methods still work if no sink overload presented 666 auto serialized1 = X!false(7,"x1").serializeToJsonString(); 667 assert(serialized1 == `"42;hello"`); 668 auto deserialized1 = deserializeJson!(X!false)(serialized1); 669 assert(deserialized1.i == 42); 670 assert(deserialized1.s == "hello"); 671 672 // sink overload takes precedence 673 auto serialized2 = X!true(7,"x2").serializeToJsonString(); 674 assert(serialized2 == `"7;x2"`); 675 auto deserialized2 = deserializeJson!(X!true)(serialized2); 676 assert(deserialized2.i == 7); 677 assert(deserialized2.s == "x2"); 678 679 // type is sink serializable, but serializer doesn't support sink 680 auto serialized3 = X!true(7,"x2").serializeToJson(); 681 assert(to!string(serialized3) == `"7;x2"`); 682 auto deserialized3 = deserializeJson!(X!true)(serialized3); 683 assert(deserialized3.i == 7); 684 assert(deserialized3.s == "x2"); 685 } 686 687 private struct Traits(T, alias POL, ATTRIBUTES...) 688 { 689 alias Type = T; 690 alias Policy = POL; 691 alias Attributes = AliasSeq!ATTRIBUTES; 692 } 693 694 private struct SubTraits(Traits, T, A...) 695 { 696 alias Type = Unqual!T; 697 alias Attributes = AliasSeq!A; 698 alias Policy = Traits.Policy; 699 alias ContainerType = Traits.Type; 700 alias ContainerAttributes = Traits.Attributes; 701 } 702 703 private template deserializeValueImpl(Serializer, alias Policy) { 704 alias _Policy = Policy; 705 static assert(Serializer.isSupportedValueType!string, "All serializers must support string values."); 706 static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values."); 707 708 // work around https://issues.dlang.org/show_bug.cgi?id=16528 709 static if (isSafeDeserializer!Serializer) { 710 T deserializeValue(T, ATTRIBUTES...)(ref Serializer ser) @safe { return deserializeValueDeduced!(T, ATTRIBUTES)(ser); } 711 } else { 712 T deserializeValue(T, ATTRIBUTES...)(ref Serializer ser) { return deserializeValueDeduced!(T, ATTRIBUTES)(ser); } 713 } 714 715 T deserializeValueDeduced(T, ATTRIBUTES...)(ref Serializer ser) if(!isMutable!T) 716 { 717 import std.algorithm.mutation : move; 718 auto ret = deserializeValue!(Unqual!T, ATTRIBUTES)(ser); 719 return () @trusted { return cast(T)ret.move; } (); 720 } 721 722 T deserializeValueDeduced(T, FIELD_ATTRIBUTES...)(ref Serializer ser) if(isMutable!T) 723 { 724 import std.typecons : BitFlags, Nullable, Typedef, TypedefType, Tuple; 725 726 alias ATTRIBUTES = AliasSeq!(FIELD_ATTRIBUTES, TypeAttributes!T); 727 728 alias Traits = .Traits!(T, _Policy, ATTRIBUTES); 729 730 static if (isPolicySerializable!(Policy, T)) { 731 alias CustomType = typeof(Policy!T.toRepresentation(T.init)); 732 return Policy!T.fromRepresentation(ser.deserializeValue!(CustomType, ATTRIBUTES)); 733 } else static if (is(T == enum)) { 734 static if (hasPolicyAttributeL!(ByNameAttribute, Policy, ATTRIBUTES)) { 735 return ser.deserializeValue!(string, ATTRIBUTES).to!T(); 736 } else static if (isSomeString!(OriginalType!T)) { 737 auto value = ser.deserializeValue!(OriginalType!T); 738 switch (value) { 739 default: 740 throw new ConvException("Unexpected enum value " ~ value.to!string); 741 static foreach (enumvalue; NoDuplicates!(EnumMembers!T)) 742 case enumvalue: return enumvalue; 743 } 744 } else { 745 return cast(T)ser.deserializeValue!(OriginalType!T); 746 } 747 } else static if (Serializer.isSupportedValueType!T) { 748 return ser.readValue!(Traits, T)(); 749 } else static if (/*isInstanceOf!(Tuple, TU)*/is(T == Tuple!TPS, TPS...)) { 750 enum fieldsCount = T.Types.length; 751 import std.algorithm.searching: all; 752 static if (all!"!a.empty"([T.fieldNames]) && 753 !hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) { 754 T ret; 755 bool[fieldsCount] set; 756 ser.readDictionary!Traits((name) { 757 switch (name) { 758 default: 759 static if (is(typeof(ser.skipValue()))) { 760 ser.skipValue(); 761 } 762 break; 763 foreach (i, TV; T.Types) { 764 enum fieldName = underscoreStrip(T.fieldNames[i]); 765 alias STraits = SubTraits!(Traits, TV); 766 case fieldName: { 767 ser.beginReadDictionaryEntry!STraits(fieldName); 768 ret[i] = ser.deserializeValue!(TV, ATTRIBUTES); 769 ser.endReadDictionaryEntry!STraits(fieldName); 770 set[i] = true; 771 } break; 772 } 773 } 774 }); 775 foreach (i, fieldName; T.fieldNames) 776 enforce(set[i], "Missing tuple field '"~fieldName~"' of type '"~T.Types[i].stringof~"' ("~Policy.stringof~")."); 777 return ret; 778 } else static if (fieldsCount == 1) { 779 return T(ser.deserializeValue!(T.Types[0], ATTRIBUTES)()); 780 } else { 781 T ret; 782 size_t currentField = 0; 783 ser.readArray!Traits((sz) { assert(sz == 0 || sz == fieldsCount); }, { 784 switch (currentField++) { 785 default: break; 786 foreach (i, TV; T.Types) { 787 alias STraits = SubTraits!(Traits, TV); 788 case i: { 789 ser.beginReadArrayEntry!STraits(i); 790 ret[i] = ser.deserializeValue!(TV, ATTRIBUTES); 791 ser.endReadArrayEntry!STraits(i); 792 } break; 793 } 794 } 795 }); 796 enforce(currentField == fieldsCount, "Missing tuple field(s) - expected '"~fieldsCount.stringof~"', received '"~currentField.stringof~"' ("~Policy.stringof~")."); 797 return ret; 798 } 799 } else static if (isStaticArray!T) { 800 alias TV = typeof(T.init[0]); 801 alias STraits = SubTraits!(Traits, TV); 802 T ret; 803 size_t i = 0; 804 ser.readArray!Traits((sz) { assert(sz == 0 || sz == T.length); }, { 805 assert(i < T.length); 806 ser.beginReadArrayEntry!STraits(i); 807 ret[i] = ser.deserializeValue!(TV, ATTRIBUTES); 808 ser.endReadArrayEntry!STraits(i); 809 i++; 810 }); 811 return ret; 812 } else static if (isDynamicArray!T) { 813 alias TV = typeof(T.init[0]); 814 alias STraits = SubTraits!(Traits, TV); 815 //auto ret = appender!T(); 816 T ret; // Cannot use appender because of DMD BUG 10690/10859/11357 817 ser.readArray!Traits((sz) @safe { ret.reserve(sz); }, () @safe { 818 size_t i = ret.length; 819 ser.beginReadArrayEntry!STraits(i); 820 static if (__traits(compiles, () @safe { ser.deserializeValue!(TV, ATTRIBUTES); })) 821 ret ~= ser.deserializeValue!(TV, ATTRIBUTES); 822 else // recursive array https://issues.dlang.org/show_bug.cgi?id=16528 823 ret ~= (() @trusted => ser.deserializeValue!(TV, ATTRIBUTES))(); 824 ser.endReadArrayEntry!STraits(i); 825 }); 826 return ret;//cast(T)ret.data; 827 } else static if (isAssociativeArray!T) { 828 alias TK = KeyType!T; 829 alias TV = ValueType!T; 830 alias STraits = SubTraits!(Traits, TV); 831 832 T ret; 833 ser.readDictionary!Traits((name) @safe { 834 TK key; 835 static if (is(TK == string) || (is(TK == enum) && is(OriginalType!TK == string))) key = cast(TK)name; 836 else static if (is(TK : real) || is(TK : long) || is(TK == enum)) key = name.to!TK; 837 else static if (isStringSerializable!TK) key = TK.fromString(name); 838 else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods."); 839 ser.beginReadDictionaryEntry!STraits(name); 840 ret[key] = ser.deserializeValue!(TV, ATTRIBUTES); 841 ser.endReadDictionaryEntry!STraits(name); 842 }); 843 return ret; 844 } else static if (isInstanceOf!(Nullable, T)) { 845 if (ser.tryReadNull!Traits()) return T.init; 846 return T(ser.deserializeValue!(typeof(T.init.get()), ATTRIBUTES)); 847 } else static if (isInstanceOf!(Typedef, T)) { 848 return T(ser.deserializeValue!(TypedefType!T, ATTRIBUTES)); 849 } else static if (is(T == BitFlags!E, E)) { 850 alias STraits = SubTraits!(Traits, E); 851 T ret; 852 size_t i = 0; 853 ser.readArray!Traits((sz) {}, { 854 ser.beginReadArrayEntry!STraits(i); 855 ret |= ser.deserializeValue!(E, ATTRIBUTES); 856 ser.endReadArrayEntry!STraits(i); 857 i++; 858 }); 859 return ret; 860 } else static if (isCustomSerializable!T) { 861 alias CustomType = typeof(T.init.toRepresentation()); 862 return T.fromRepresentation(ser.deserializeValue!(CustomType, ATTRIBUTES)); 863 } else static if (isISOExtStringSerializable!T) { 864 return T.fromISOExtString(ser.readValue!(Traits, string)()); 865 } else static if (isStringSerializable!T) { 866 return T.fromString(ser.readValue!(Traits, string)()); 867 } else static if (is(T == struct) || is(T == class)) { 868 static if (is(T == class)) { 869 if (ser.tryReadNull!Traits()) return null; 870 } 871 872 T ret; 873 string name; 874 bool[getExpandedFieldsData!(T, SerializableFields!(T, Policy)).length] set; 875 static if (is(T == class)) ret = new T; 876 877 void safeSetMember(string mname, U)(ref T value, U fval) 878 @safe { 879 static if (__traits(compiles, () @safe { __traits(getMember, value, mname) = fval; })) 880 __traits(getMember, value, mname) = fval; 881 else { 882 pragma(msg, "Warning: Setter for "~fullyQualifiedName!T~"."~mname~" is not @safe"); 883 () @trusted { __traits(getMember, value, mname) = fval; } (); 884 } 885 } 886 887 static if (hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) { 888 size_t idx = 0; 889 ser.readArray!Traits((sz){}, { 890 static if (hasSerializableFields!(T, Policy)) { 891 switch (idx++) { 892 default: break; 893 foreach (i, FD; getExpandedFieldsData!(T, SerializableFields!(T, Policy))) { 894 enum mname = FD[0]; 895 enum msindex = FD[1]; 896 alias MT = AliasSeq!(__traits(getMember, T, mname)); 897 alias MTI = MT[msindex]; 898 alias TMTI = typeof(MTI); 899 alias TMTIA = AliasSeq!(__traits(getAttributes, MTI)); 900 alias STraits = SubTraits!(Traits, TMTI, TMTIA); 901 902 case i: 903 static if (hasPolicyAttribute!(OptionalAttribute, Policy, MTI)) 904 if (ser.tryReadNull!STraits()) return; 905 set[i] = true; 906 ser.beginReadArrayEntry!STraits(i); 907 static if (!isBuiltinTuple!(T, mname)) { 908 safeSetMember!mname(ret, ser.deserializeValue!(TMTI, TMTIA)); 909 } else { 910 __traits(getMember, ret, mname)[msindex] = ser.deserializeValue!(TMTI, TMTIA); 911 } 912 ser.endReadArrayEntry!STraits(i); 913 break; 914 } 915 } 916 } else { 917 pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields."); 918 } 919 }); 920 } else { 921 ser.readDictionary!Traits((name) { 922 static if (hasSerializableFields!(T, Policy)) { 923 switch (name) { 924 default: 925 static if (is(typeof(ser.skipValue()))) { 926 ser.skipValue(); 927 } 928 break; 929 foreach (i, mname; SerializableFields!(T, Policy)) { 930 alias TM = AliasSeq!(typeof(__traits(getMember, T, mname))); 931 static if (__traits(getOverloads, T, mname).length > 0) { 932 // combine attributes of overload sets 933 alias getAtts(alias ovl) = __traits(getAttributes, ovl); 934 alias TA = staticMap!(getAtts, __traits(getOverloads, T, mname)); 935 } else { 936 alias TA = AliasSeq!(__traits(getAttributes, AliasSeq!(__traits(getMember, T, mname))[0])); 937 } 938 alias STraits = SubTraits!(Traits, TM, TA); 939 enum fname = getPolicyAttribute!(fullyQualifiedName!T~"."~mname, NameAttribute, Policy, TA)(NameAttribute!DefaultPolicy(underscoreStrip(mname))).name; 940 case fname: 941 static if (hasPolicyAttribute!(OptionalAttribute, Policy, AliasSeq!(__traits(getMember, T, mname))[0])) 942 if (ser.tryReadNull!STraits()) return; 943 set[i] = true; 944 ser.beginReadDictionaryEntry!STraits(fname); 945 static if (!isBuiltinTuple!(T, mname)) { 946 safeSetMember!mname(ret, ser.deserializeValue!(TM, TA)); 947 } else { 948 __traits(getMember, ret, mname) = ser.deserializeValue!(Tuple!TM, TA); 949 } 950 ser.endReadDictionaryEntry!STraits(fname); 951 break; 952 } 953 } 954 } else { 955 pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields."); 956 } 957 }); 958 } 959 foreach (i, mname; SerializableFields!(T, Policy)) 960 static if (!hasPolicyAttribute!(OptionalAttribute, Policy, AliasSeq!(__traits(getMember, T, mname))[0])) 961 enforce(set[i], "Missing non-optional field '"~mname~"' of type '"~T.stringof~"' ("~Policy.stringof~")."); 962 return ret; 963 } else static if (isPointer!T) { 964 if (ser.tryReadNull!Traits()) return null; 965 alias PT = PointerTarget!T; 966 auto ret = new PT; 967 *ret = ser.deserializeValue!(PT, ATTRIBUTES); 968 return ret; 969 } else static if (is(T == bool) || is(T : real) || is(T : long)) { 970 return to!T(ser.deserializeValue!string()); 971 } else static assert(false, "Unsupported serialization type: " ~ T.stringof); 972 } 973 } 974 975 976 /** 977 Attribute for overriding the field name during (de-)serialization. 978 979 Note that without the `@name` attribute there is a shorter alternative 980 for using names that collide with a D keyword. A single trailing 981 underscore will automatically be stripped when determining a field 982 name. 983 */ 984 NameAttribute!Policy name(alias Policy = DefaultPolicy)(string name) 985 { 986 return NameAttribute!Policy(name); 987 } 988 /// 989 unittest { 990 struct CustomPolicy {} 991 992 struct Test { 993 // serialized as "screen-size": 994 @name("screen-size") int screenSize; 995 996 // serialized as "print-size" by default, 997 // but as "PRINTSIZE" if CustomPolicy is used for serialization. 998 @name("print-size") 999 @name!CustomPolicy("PRINTSIZE") 1000 int printSize; 1001 1002 // serialized as "version" 1003 int version_; 1004 } 1005 } 1006 1007 1008 /** 1009 Attribute marking a field as optional during deserialization. 1010 */ 1011 @property OptionalAttribute!Policy optional(alias Policy = DefaultPolicy)() 1012 { 1013 return OptionalAttribute!Policy(); 1014 } 1015 /// 1016 unittest { 1017 struct Test { 1018 // does not need to be present during deserialization 1019 @optional int screenSize = 100; 1020 } 1021 } 1022 1023 1024 /** 1025 Attribute for marking non-serialized fields. 1026 */ 1027 @property IgnoreAttribute!Policy ignore(alias Policy = DefaultPolicy)() 1028 { 1029 return IgnoreAttribute!Policy(); 1030 } 1031 /// 1032 unittest { 1033 struct Test { 1034 // is neither serialized not deserialized 1035 @ignore int screenSize; 1036 } 1037 } 1038 /// 1039 unittest { 1040 template CustomPolicy(T) { 1041 // ... 1042 } 1043 1044 struct Test { 1045 // not (de)serialized for serializeWithPolicy!(Test, CustomPolicy) 1046 // but for other policies or when serialized without a policy 1047 @ignore!CustomPolicy int screenSize; 1048 } 1049 } 1050 1051 1052 /** 1053 Attribute for forcing serialization of enum fields by name instead of by value. 1054 */ 1055 @property ByNameAttribute!Policy byName(alias Policy = DefaultPolicy)() 1056 { 1057 return ByNameAttribute!Policy(); 1058 } 1059 /// 1060 unittest { 1061 enum Color { 1062 red, 1063 green, 1064 blue 1065 } 1066 1067 struct Test { 1068 // serialized as an int (e.g. 1 for Color.green) 1069 Color color; 1070 // serialized as a string (e.g. "green" for Color.green) 1071 @byName Color namedColor; 1072 // serialized as array of ints 1073 Color[] colorArray; 1074 // serialized as array of strings 1075 @byName Color[] namedColorArray; 1076 } 1077 } 1078 1079 1080 /** 1081 Attribute for representing a struct/class as an array instead of an object. 1082 1083 Usually structs and class objects are serialized as dictionaries mapping 1084 from field name to value. Using this attribute, they will be serialized 1085 as a flat array instead. Note that changing the layout will make any 1086 already serialized data mismatch when this attribute is used. 1087 */ 1088 @property AsArrayAttribute!Policy asArray(alias Policy = DefaultPolicy)() 1089 { 1090 return AsArrayAttribute!Policy(); 1091 } 1092 /// 1093 unittest { 1094 struct Fields { 1095 int f1; 1096 string f2; 1097 double f3; 1098 } 1099 1100 struct Test { 1101 // serialized as name:value pairs ["f1": int, "f2": string, "f3": double] 1102 Fields object; 1103 // serialized as a sequential list of values [int, string, double] 1104 @asArray Fields array; 1105 } 1106 1107 import dub.internal.vibecompat.data.json; 1108 static assert(is(typeof(serializeToJson(Test())))); 1109 } 1110 1111 1112 /** 1113 Makes this nullable as if it is not a nullable to the serializer. Ignores the field completely when it is null. 1114 1115 Works with Nullable!classes and Nullable!structs. Behavior is undefined if this is applied to other types. 1116 1117 Implicitly marks this as optional for deserialization. (Keeps the struct default value when not present in serialized value) 1118 */ 1119 @property EmbedNullableIgnoreNullAttribute!Policy embedNullable(alias Policy = DefaultPolicy)() 1120 { 1121 return EmbedNullableIgnoreNullAttribute!Policy(); 1122 } 1123 /// 1124 unittest { 1125 import std.typecons : Nullable; 1126 1127 struct Test { 1128 // Not serialized at all if null, ignored on deserialization if not present. 1129 @embedNullable Nullable!int field; 1130 } 1131 } 1132 1133 1134 /// 1135 enum FieldExistence 1136 { 1137 missing, 1138 exists, 1139 defer 1140 } 1141 1142 /// User defined attribute (not intended for direct use) 1143 struct NameAttribute(alias POLICY) { alias Policy = POLICY; string name; } 1144 /// ditto 1145 struct OptionalAttribute(alias POLICY) { alias Policy = POLICY; } 1146 /// ditto 1147 struct IgnoreAttribute(alias POLICY) { alias Policy = POLICY; } 1148 /// ditto 1149 struct ByNameAttribute(alias POLICY) { alias Policy = POLICY; } 1150 /// ditto 1151 struct AsArrayAttribute(alias POLICY) { alias Policy = POLICY; } 1152 /// ditto 1153 struct EmbedNullableIgnoreNullAttribute(alias POLICY) { alias Policy = POLICY; } 1154 1155 /** 1156 Checks if a given type has a custom serialization representation. 1157 1158 A class or struct type is custom serializable if it defines a pair of 1159 `toRepresentation`/`fromRepresentation` methods. Any class or 1160 struct type that has this trait will be serialized by using the return 1161 value of it's `toRepresentation` method instead of the original value. 1162 1163 This trait has precedence over `isISOExtStringSerializable` and 1164 `isStringSerializable`. 1165 */ 1166 template isCustomSerializable(T) 1167 { 1168 enum bool isCustomSerializable = is(typeof(T.init.toRepresentation())) && is(typeof(T.fromRepresentation(T.init.toRepresentation())) == T); 1169 } 1170 /// 1171 unittest { 1172 // represented as a single uint when serialized 1173 static struct S { 1174 ushort x, y; 1175 1176 uint toRepresentation() const { return x + (y << 16); } 1177 static S fromRepresentation(uint i) { return S(i & 0xFFFF, i >> 16); } 1178 } 1179 1180 static assert(isCustomSerializable!S); 1181 } 1182 1183 1184 /** 1185 Checks if a given type has an ISO extended string serialization representation. 1186 1187 A class or struct type is ISO extended string serializable if it defines a 1188 pair of `toISOExtString`/`fromISOExtString` methods. Any class or 1189 struct type that has this trait will be serialized by using the return 1190 value of it's `toISOExtString` method instead of the original value. 1191 1192 This is mainly useful for supporting serialization of the the date/time 1193 types in `std.datetime`. 1194 1195 This trait has precedence over `isStringSerializable`. 1196 */ 1197 template isISOExtStringSerializable(T) 1198 { 1199 enum bool isISOExtStringSerializable = is(typeof(T.init.toISOExtString()) : string) && is(typeof(T.fromISOExtString("")) : T); 1200 } 1201 /// 1202 unittest { 1203 import std.datetime; 1204 1205 static assert(isISOExtStringSerializable!DateTime); 1206 static assert(isISOExtStringSerializable!SysTime); 1207 1208 // represented as an ISO extended string when serialized 1209 static struct S { 1210 // dummy example implementations 1211 string toISOExtString() const { return ""; } 1212 static S fromISOExtString(string s) { return S.init; } 1213 } 1214 1215 static assert(isISOExtStringSerializable!S); 1216 } 1217 1218 1219 /** 1220 Checks if a given type has a string serialization representation. 1221 1222 A class or struct type is string serializable if it defines a pair of 1223 `toString`/`fromString` methods. Any class or struct type that 1224 has this trait will be serialized by using the return value of it's 1225 `toString` method instead of the original value. 1226 */ 1227 template isStringSerializable(T) 1228 { 1229 enum bool isStringSerializable = is(typeof(T.init.toString()) : string) && is(typeof(T.fromString("")) : T); 1230 } 1231 /// 1232 unittest { 1233 import std.conv; 1234 1235 // represented as a string when serialized 1236 static struct S { 1237 int value; 1238 1239 // dummy example implementations 1240 string toString() const { return value.to!string(); } 1241 static S fromString(string s) { return S(s.to!int()); } 1242 } 1243 1244 static assert(isStringSerializable!S); 1245 } 1246 1247 1248 /** Default policy (performs no customization). 1249 */ 1250 template DefaultPolicy(T) 1251 { 1252 } 1253 1254 /** 1255 Checks if a given policy supports custom serialization for a given type. 1256 1257 A class or struct type is custom serializable according to a policy if 1258 the policy defines a pair of `toRepresentation`/`fromRepresentation` 1259 functions. Any class or struct type that has this trait for the policy supplied to 1260 `serializeWithPolicy` will be serialized by using the return value of the 1261 policy `toRepresentation` function instead of the original value. 1262 1263 This trait has precedence over `isCustomSerializable`, 1264 `isISOExtStringSerializable` and `isStringSerializable`. 1265 1266 See_Also: `vibe.data.serialization.serializeWithPolicy` 1267 */ 1268 template isPolicySerializable(alias Policy, T) 1269 { 1270 enum bool isPolicySerializable = is(typeof(Policy!T.toRepresentation(T.init))) && 1271 is(typeof(Policy!T.fromRepresentation(Policy!T.toRepresentation(T.init))) : T); 1272 } 1273 /// 1274 unittest { 1275 import std.conv; 1276 1277 // represented as the boxed value when serialized 1278 static struct Box(T) { 1279 T value; 1280 } 1281 1282 template BoxPol(S) 1283 { 1284 auto toRepresentation(S s) { 1285 return s.value; 1286 } 1287 1288 S fromRepresentation(typeof(S.init.value) v) { 1289 return S(v); 1290 } 1291 } 1292 static assert(isPolicySerializable!(BoxPol, Box!int)); 1293 } 1294 1295 1296 /** 1297 Chains serialization policy. 1298 1299 Constructs a serialization policy that given a type `T` will apply the 1300 first compatible policy `toRepresentation` and `fromRepresentation` 1301 functions. Policies are evaluated left-to-right according to 1302 `isPolicySerializable`. 1303 1304 See_Also: `vibe.data.serialization.serializeWithPolicy` 1305 */ 1306 template ChainedPolicy(alias Primary, Fallbacks...) 1307 { 1308 static if (Fallbacks.length == 0) { 1309 alias ChainedPolicy = Primary; 1310 } else { 1311 alias ChainedPolicy = ChainedPolicy!(ChainedPolicyImpl!(Primary, Fallbacks[0]), Fallbacks[1..$]); 1312 } 1313 } 1314 /// 1315 unittest { 1316 import std.conv; 1317 1318 // To be represented as the boxed value when serialized 1319 static struct Box(T) { 1320 T value; 1321 } 1322 // Also to berepresented as the boxed value when serialized, but has 1323 // a different way to access the value. 1324 static struct Box2(T) { 1325 private T v; 1326 ref T get() { 1327 return v; 1328 } 1329 } 1330 template BoxPol(S) 1331 { 1332 auto toRepresentation(S s) { 1333 return s.value; 1334 } 1335 1336 S fromRepresentation(typeof(toRepresentation(S.init)) v) { 1337 return S(v); 1338 } 1339 } 1340 template Box2Pol(S) 1341 { 1342 auto toRepresentation(S s) { 1343 return s.get(); 1344 } 1345 1346 S fromRepresentation(typeof(toRepresentation(S.init)) v) { 1347 S s; 1348 s.get() = v; 1349 return s; 1350 } 1351 } 1352 alias ChainPol = ChainedPolicy!(BoxPol, Box2Pol); 1353 static assert(!isPolicySerializable!(BoxPol, Box2!int)); 1354 static assert(!isPolicySerializable!(Box2Pol, Box!int)); 1355 static assert(isPolicySerializable!(ChainPol, Box!int)); 1356 static assert(isPolicySerializable!(ChainPol, Box2!int)); 1357 } 1358 1359 private template ChainedPolicyImpl(alias Primary, alias Fallback) 1360 { 1361 template Pol(T) 1362 { 1363 static if (isPolicySerializable!(Primary, T)) { 1364 alias toRepresentation = Primary!T.toRepresentation; 1365 alias fromRepresentation = Primary!T.fromRepresentation; 1366 } else { 1367 alias toRepresentation = Fallback!T.toRepresentation; 1368 alias fromRepresentation = Fallback!T.fromRepresentation; 1369 } 1370 } 1371 alias ChainedPolicyImpl = Pol; 1372 } 1373 1374 private template isBuiltinTuple(T, string member) 1375 { 1376 alias TM = AliasSeq!(typeof(__traits(getMember, T.init, member))); 1377 static if (TM.length > 1) enum isBuiltinTuple = true; 1378 else static if (is(typeof(__traits(getMember, T.init, member)) == TM[0])) 1379 enum isBuiltinTuple = false; 1380 else enum isBuiltinTuple = true; // single-element tuple 1381 } 1382 1383 // heuristically determines @safe'ty of the serializer by testing readValue and writeValue for type int 1384 private template isSafeSerializer(S) 1385 { 1386 alias T = Traits!(int, DefaultPolicy); 1387 static if (__traits(hasMember, S, "writeValue")) 1388 enum isSafeSerializer = __traits(compiles, (S s) @safe { s.writeValue!T(42); }); 1389 else static assert(0, "Serializer is missing required writeValue method"); 1390 } 1391 1392 // heuristically determines @safe'ty of the deserializer by testing readValue and writeValue for type int 1393 private template isSafeDeserializer(S) 1394 { 1395 alias T = Traits!(int, DefaultPolicy); 1396 static if (__traits(hasMember, S, "readValue")) 1397 enum isSafeDeserializer = __traits(compiles, (S s) @safe { s.readValue!(T, int)(); }); 1398 else static assert(0, "Deserializer is missing required readValue method"); 1399 } 1400 1401 private template hasAttribute(T, alias decl) { 1402 // detect overload sets and satisfy if any overload has the attribute 1403 static if (is(typeof(__traits(getMember, __traits(parent, decl), __traits(identifier, decl)))) 1404 && __traits(getOverloads, __traits(parent, decl), __traits(identifier, decl)).length > 1) 1405 { 1406 enum match(alias ovl) = findFirstUDA!(T, ovl).found; 1407 enum hasAttribute = anySatisfy!(match, __traits(getOverloads, __traits(parent, decl), __traits(identifier, decl))); 1408 } else { 1409 enum hasAttribute = findFirstUDA!(T, decl).found; 1410 } 1411 } 1412 1413 unittest { 1414 @asArray int i1; 1415 static assert(hasAttribute!(AsArrayAttribute!DefaultPolicy, i1)); 1416 int i2; 1417 static assert(!hasAttribute!(AsArrayAttribute!DefaultPolicy, i2)); 1418 1419 static struct S { 1420 @asArray void foo(int) {} 1421 void foo() {} 1422 1423 void bar(int) {} 1424 @asArray void bar() {} 1425 } 1426 1427 static assert(hasAttribute!(AsArrayAttribute!DefaultPolicy, S.foo)); 1428 static assert(hasAttribute!(AsArrayAttribute!DefaultPolicy, S.bar)); 1429 } 1430 1431 private template hasPolicyAttribute(alias T, alias POLICY, alias decl) 1432 { 1433 // __traits(identifier) to hack around T being a template and not a type 1434 // this if makes hasPolicyAttribute!(OptionalAttribute) == true when EmbedNullableIgnoreNullAttribute is present. 1435 static if (__traits(identifier, T) == __traits(identifier, OptionalAttribute)) 1436 enum hasPolicyAttribute = hasPolicyAttributeImpl!(T, POLICY, decl) 1437 || hasPolicyAttributeImpl!(EmbedNullableIgnoreNullAttribute, POLICY, decl); 1438 else 1439 enum hasPolicyAttribute = hasPolicyAttributeImpl!(T, POLICY, decl); 1440 } 1441 1442 private template hasPolicyAttributeImpl(alias T, alias POLICY, alias decl) 1443 { 1444 enum hasPolicyAttributeImpl = hasAttribute!(T!POLICY, decl) || hasAttribute!(T!DefaultPolicy, decl); 1445 } 1446 1447 unittest { 1448 import std.typecons : Nullable; 1449 1450 template CP(T) {} 1451 @asArray!CP int i1; 1452 @asArray int i2; 1453 int i3; 1454 @embedNullable Nullable!int i4; 1455 1456 static assert(hasPolicyAttribute!(AsArrayAttribute, CP, i1)); 1457 static assert(hasPolicyAttribute!(AsArrayAttribute, CP, i2)); 1458 static assert(!hasPolicyAttribute!(AsArrayAttribute, CP, i3)); 1459 static assert(!hasPolicyAttribute!(AsArrayAttribute, DefaultPolicy, i1)); 1460 static assert(hasPolicyAttribute!(AsArrayAttribute, DefaultPolicy, i2)); 1461 static assert(!hasPolicyAttribute!(AsArrayAttribute, DefaultPolicy, i3)); 1462 static assert(hasPolicyAttribute!(EmbedNullableIgnoreNullAttribute, DefaultPolicy, i4)); 1463 static assert(hasPolicyAttribute!(OptionalAttribute, DefaultPolicy, i4)); 1464 static assert(!hasPolicyAttribute!(IgnoreAttribute, DefaultPolicy, i4)); 1465 } 1466 1467 1468 private template hasAttributeL(T, ATTRIBUTES...) { 1469 static if (ATTRIBUTES.length == 1) { 1470 enum hasAttributeL = is(typeof(ATTRIBUTES[0]) == T); 1471 } else static if (ATTRIBUTES.length > 1) { 1472 enum hasAttributeL = hasAttributeL!(T, ATTRIBUTES[0 .. $/2]) || hasAttributeL!(T, ATTRIBUTES[$/2 .. $]); 1473 } else { 1474 enum hasAttributeL = false; 1475 } 1476 } 1477 1478 unittest { 1479 static assert(hasAttributeL!(AsArrayAttribute!DefaultPolicy, byName, asArray)); 1480 static assert(!hasAttributeL!(AsArrayAttribute!DefaultPolicy, byName)); 1481 } 1482 1483 private template hasPolicyAttributeL(alias T, alias POLICY, ATTRIBUTES...) 1484 { 1485 enum hasPolicyAttributeL = hasAttributeL!(T!POLICY, ATTRIBUTES) || hasAttributeL!(T!DefaultPolicy, ATTRIBUTES); 1486 } 1487 1488 private template TypeAttributes(T) { 1489 static if (__traits(compiles, __traits(identifier, T))) 1490 alias TypeAttributes = AliasSeq!(__traits(getAttributes, T)); 1491 else // D < 2.101.0 raises an error when attempting to get attributes of built-in types. 1492 alias TypeAttributes = AliasSeq!(); 1493 } 1494 1495 private static auto getPolicyAttribute(string field, alias Attribute, alias Policy, Attributes...)(Attribute!DefaultPolicy default_value) 1496 { 1497 enum match(alias A) = matchesUDAKind!(A, Attribute!Policy); 1498 enum PA = Filter!(match, Attributes); 1499 static if (PA.length) { 1500 static foreach (i; 1 .. PA.length) 1501 static assert(PA[i] == PA[0], "Mismatching " ~ Attribute!Policy.stringof ~ " attributes for serialized field " ~ field); 1502 return PA[0]; 1503 } else { 1504 enum matchDef(alias A) = matchesUDAKind(A, Attribute!DefaultPolicy); 1505 enum PAD = Filter!(match, Attributes); 1506 static if (PAD.length) { 1507 static foreach (i; 1 .. PAD.length) 1508 static assert(PAD[i] == PAD[0], "Mismatching " ~ Attribute!DefaultPolicy.stringof ~ " attributes for serialized field " ~ field); 1509 return PAD[0]; 1510 } else return default_value; 1511 } 1512 } 1513 1514 private string underscoreStrip(string field_name) 1515 @safe nothrow @nogc { 1516 if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name; 1517 else return field_name[0 .. $-1]; 1518 } 1519 1520 1521 private template hasSerializableFields(T, alias POLICY, size_t idx = 0) 1522 { 1523 enum hasSerializableFields = SerializableFields!(T, POLICY).length > 0; 1524 /*static if (idx < __traits(allMembers, T).length) { 1525 enum mname = __traits(allMembers, T)[idx]; 1526 static if (!isRWPlainField!(T, mname) && !isRWField!(T, mname)) enum hasSerializableFields = hasSerializableFields!(T, idx+1); 1527 else static if (hasAttribute!(IgnoreAttribute, __traits(getMember, T, mname))) enum hasSerializableFields = hasSerializableFields!(T, idx+1); 1528 else enum hasSerializableFields = true; 1529 } else enum hasSerializableFields = false;*/ 1530 } 1531 1532 private template SerializableFields(COMPOSITE, alias POLICY) 1533 { 1534 alias SerializableFields = FilterSerializableFields!(COMPOSITE, POLICY, __traits(allMembers, COMPOSITE)); 1535 } 1536 1537 private template FilterSerializableFields(COMPOSITE, alias POLICY, FIELDS...) 1538 { 1539 static if (FIELDS.length > 1) { 1540 alias FilterSerializableFields = AliasSeq!( 1541 FilterSerializableFields!(COMPOSITE, POLICY, FIELDS[0 .. $/2]), 1542 FilterSerializableFields!(COMPOSITE, POLICY, FIELDS[$/2 .. $])); 1543 } else static if (FIELDS.length == 1) { 1544 alias T = COMPOSITE; 1545 enum mname = FIELDS[0]; 1546 static if (isRWPlainField!(T, mname) || isRWField!(T, mname)) { 1547 alias Tup = AliasSeq!(__traits(getMember, COMPOSITE, FIELDS[0])); 1548 static if (Tup.length != 1) { 1549 alias FilterSerializableFields = AliasSeq!(mname); 1550 } else static if (__traits(getOverloads, T, mname).length > 1) { 1551 enum ignored(alias sym) = hasPolicyAttribute!(IgnoreAttribute, POLICY, sym); 1552 static if (!anySatisfy!(ignored, __traits(getOverloads, T, mname))) 1553 alias FilterSerializableFields = AliasSeq!(mname); 1554 else 1555 alias FilterSerializableFields = AliasSeq!(); 1556 } else { 1557 static if (!hasPolicyAttribute!(IgnoreAttribute, POLICY, __traits(getMember, T, mname))) 1558 alias FilterSerializableFields = AliasSeq!(mname); 1559 else 1560 alias FilterSerializableFields = AliasSeq!(); 1561 } 1562 } else alias FilterSerializableFields = AliasSeq!(); 1563 } else alias FilterSerializableFields = AliasSeq!(); 1564 } 1565 1566 private size_t getExpandedFieldCount(T, FIELDS...)() 1567 { 1568 size_t ret = 0; 1569 foreach (F; FIELDS) ret += AliasSeq!(__traits(getMember, T, F)).length; 1570 return ret; 1571 } 1572 1573 private template getExpandedFieldsData(T, FIELDS...) 1574 { 1575 import std.meta : aliasSeqOf, staticMap; 1576 import std.range : repeat, zip, iota; 1577 1578 enum subfieldsCount(alias F) = AliasSeq!(__traits(getMember, T, F)).length; 1579 alias processSubfield(alias F) = aliasSeqOf!(zip(repeat(F), iota(subfieldsCount!F))); 1580 alias getExpandedFieldsData = staticMap!(processSubfield, FIELDS); 1581 } 1582 1583 /// Uses Base64 representation for `ubyte[]` instead of `to!string` 1584 public class Base64ArrayPolicy (R) if (isArray!R && is(ElementType!R : ubyte)) 1585 { 1586 public static string toRepresentation (in R data) @safe pure 1587 { 1588 import std.base64 : Base64; 1589 return Base64.encode(data); 1590 } 1591 1592 public static ubyte[] fromRepresentation (in string data) @safe pure 1593 { 1594 import std.base64 : Base64; 1595 return Base64.decode(data); 1596 } 1597 } 1598 1599 /******************************************************************************/ 1600 /* General serialization unit testing */ 1601 /******************************************************************************/ 1602 1603 version (unittest) { 1604 static assert(isSafeSerializer!TestSerializer); 1605 static assert(isSafeDeserializer!TestSerializer); 1606 1607 private struct TestSerializer { 1608 import std.array, std.conv, std.range, std.string, std.typecons; 1609 1610 string result; 1611 1612 enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null)) || is(T == float) || is (T == int); 1613 1614 template unqualSeq(Specs...) 1615 { 1616 static if (Specs.length == 0) alias unqualSeq = AliasSeq!(); 1617 else static if (is(Specs[0])) alias unqualSeq = AliasSeq!(Unqual!(Specs[0]), unqualSeq!(Specs[1 .. $])); 1618 else alias unqualSeq = AliasSeq!(Specs[0], unqualSeq!(Specs[1 .. $])); 1619 } 1620 1621 template unqualType(T) { 1622 static if (isAssociativeArray!T) alias unqualType = Unqual!(ValueType!T)[Unqual!(KeyType!T)]; 1623 else static if (isTuple!T) alias unqualType = Tuple!(unqualSeq!(TemplateArgsOf!T)); 1624 else static if (isArray!T && !isSomeString!T) alias unqualType = Unqual!(ElementType!T)[]; 1625 else alias unqualType = Unqual!T; 1626 } 1627 1628 string getSerializedResult() @safe { return result; } 1629 void beginWriteDictionary(Traits)() { result ~= "D("~unqualType!(Traits.Type).mangleof~"){"; } 1630 void endWriteDictionary(Traits)() { result ~= "}D("~unqualType!(Traits.Type).mangleof~")"; } 1631 void beginWriteDictionaryEntry(Traits)(string name) { result ~= "DE("~unqualType!(Traits.Type).mangleof~","~name~")("; } 1632 void endWriteDictionaryEntry(Traits)(string name) { result ~= ")DE("~unqualType!(Traits.Type).mangleof~","~name~")"; } 1633 void beginWriteArray(Traits)(size_t length) { result ~= "A("~unqualType!(Traits.Type).mangleof~")["~length.to!string~"]["; } 1634 void endWriteArray(Traits)() { result ~= "]A("~unqualType!(Traits.Type).mangleof~")"; } 1635 void beginWriteArrayEntry(Traits)(size_t i) { result ~= "AE("~unqualType!(Traits.Type).mangleof~","~i.to!string~")("; } 1636 void endWriteArrayEntry(Traits)(size_t i) { result ~= ")AE("~unqualType!(Traits.Type).mangleof~","~i.to!string~")"; } 1637 void writeValue(Traits, T)(T value) { 1638 if (is(T == typeof(null))) result ~= "null"; 1639 else { 1640 assert(isSupportedValueType!(unqualType!T)); 1641 result ~= "V("~(unqualType!T).mangleof~")("~value.to!string~")"; 1642 } 1643 } 1644 1645 // deserialization 1646 void readDictionary(Traits)(scope void delegate(string) @safe entry_callback) 1647 { 1648 skip("D("~unqualType!(Traits.Type).mangleof~"){"); 1649 while (result.startsWith("DE(")) { 1650 result = result[3 .. $]; 1651 auto idx = result.indexOf(','); 1652 auto idx2 = result.indexOf(")("); 1653 assert(idx > 0 && idx2 > idx); 1654 auto t = result[0 .. idx]; 1655 auto n = result[idx+1 .. idx2]; 1656 result = result[idx2+2 .. $]; 1657 entry_callback(n); 1658 skip(")DE("~t~","~n~")"); 1659 } 1660 skip("}D("~unqualType!(Traits.Type).mangleof~")"); 1661 } 1662 1663 void beginReadDictionaryEntry(Traits)(string name) {} 1664 void endReadDictionaryEntry(Traits)(string name) {} 1665 1666 void readArray(Traits)(scope void delegate(size_t) @safe size_callback, scope void delegate() @safe entry_callback) 1667 { 1668 skip("A("~unqualType!(Traits.Type).mangleof~")["); 1669 auto bidx = result.indexOf("]["); 1670 assert(bidx > 0); 1671 auto cnt = result[0 .. bidx].to!size_t; 1672 result = result[bidx+2 .. $]; 1673 1674 size_t i = 0; 1675 while (result.startsWith("AE(")) { 1676 result = result[3 .. $]; 1677 auto idx = result.indexOf(','); 1678 auto idx2 = result.indexOf(")("); 1679 assert(idx > 0 && idx2 > idx); 1680 auto t = result[0 .. idx]; 1681 auto n = result[idx+1 .. idx2]; 1682 result = result[idx2+2 .. $]; 1683 assert(n == i.to!string); 1684 entry_callback(); 1685 skip(")AE("~t~","~n~")"); 1686 i++; 1687 } 1688 skip("]A("~unqualType!(Traits.Type).mangleof~")"); 1689 1690 assert(i == cnt); 1691 } 1692 1693 void beginReadArrayEntry(Traits)(size_t index) {} 1694 void endReadArrayEntry(Traits)(size_t index) {} 1695 1696 T readValue(Traits, T)() 1697 { 1698 skip("V("~unqualType!T.mangleof~")("); 1699 auto idx = result.indexOf(')'); 1700 assert(idx >= 0); 1701 auto ret = result[0 .. idx].to!T; 1702 result = result[idx+1 .. $]; 1703 return ret; 1704 } 1705 1706 void skip(string prefix) 1707 @safe { 1708 assert(result.startsWith(prefix), prefix ~ " vs. " ~ result); 1709 result = result[prefix.length .. $]; 1710 } 1711 1712 bool tryReadNull(Traits)() 1713 { 1714 if (result.startsWith("null")) { 1715 result = result[4 .. $]; 1716 return true; 1717 } else return false; 1718 } 1719 } 1720 } 1721 1722 unittest { // basic serialization behavior 1723 import std.typecons : Nullable; 1724 1725 static void test(T)(auto ref T value, string expected) { 1726 assert(serialize!TestSerializer(value) == expected, serialize!TestSerializer(value)); 1727 static if (isPointer!T) { 1728 if (value) assert(*deserialize!(TestSerializer, T)(expected) == *value); 1729 else assert(deserialize!(TestSerializer, T)(expected) is null); 1730 } else static if (is(T == Nullable!U, U)) { 1731 if (value.isNull()) assert(deserialize!(TestSerializer, T)(expected).isNull); 1732 else assert(deserialize!(TestSerializer, T)(expected) == value); 1733 } else assert(deserialize!(TestSerializer, T)(expected) == value); 1734 } 1735 1736 test("hello", "V(Aya)(hello)"); 1737 test(12, "V(i)(12)"); 1738 test(12.0, "V(Aya)(12)"); 1739 test(12.0f, "V(f)(12)"); 1740 assert(serialize!TestSerializer(null) == "null"); 1741 test(["hello", "world"], "A(AAya)[2][AE(Aya,0)(V(Aya)(hello))AE(Aya,0)AE(Aya,1)(V(Aya)(world))AE(Aya,1)]A(AAya)"); 1742 string mangleOfAA = (string[string]).mangleof; 1743 test(["hello": "world"], "D(" ~ mangleOfAA ~ "){DE(Aya,hello)(V(Aya)(world))DE(Aya,hello)}D(" ~ mangleOfAA ~ ")"); 1744 test(cast(int*)null, "null"); 1745 int i = 42; 1746 test(&i, "V(i)(42)"); 1747 Nullable!int j; 1748 test(j, "null"); 1749 j = 42; 1750 test(j, "V(i)(42)"); 1751 } 1752 1753 unittest { // basic user defined types 1754 static struct S { string f; } 1755 enum Sm = S.mangleof; 1756 auto s = S("hello"); 1757 enum s_ser = "D("~Sm~"){DE(Aya,f)(V(Aya)(hello))DE(Aya,f)}D("~Sm~")"; 1758 assert(serialize!TestSerializer(s) == s_ser, serialize!TestSerializer(s)); 1759 assert(deserialize!(TestSerializer, S)(s_ser) == s); 1760 1761 static class C { string f; } 1762 enum Cm = C.mangleof; 1763 C c; 1764 assert(serialize!TestSerializer(c) == "null"); 1765 c = new C; 1766 c.f = "hello"; 1767 enum c_ser = "D("~Cm~"){DE(Aya,f)(V(Aya)(hello))DE(Aya,f)}D("~Cm~")"; 1768 assert(serialize!TestSerializer(c) == c_ser); 1769 assert(deserialize!(TestSerializer, C)(c_ser).f == c.f); 1770 1771 enum E { hello, world } 1772 assert(serialize!TestSerializer(E.hello) == "V(i)(0)"); 1773 assert(serialize!TestSerializer(E.world) == "V(i)(1)"); 1774 } 1775 1776 unittest { // tuple serialization 1777 import std.typecons : Tuple; 1778 1779 static struct S(T...) { T f; } 1780 enum Sm = S!(int, string).mangleof; 1781 enum Tum = Tuple!(int, string).mangleof; 1782 const s = S!(int, string)(42, "hello"); 1783 1784 const ss = serialize!TestSerializer(s); 1785 const es = "D("~Sm~"){DE("~Tum~",f)(A("~Tum~")[2][AE(i,0)(V(i)(42))AE(i,0)AE(Aya,1)(V(Aya)(hello))AE(Aya,1)]A("~Tum~"))DE("~Tum~",f)}D("~Sm~")"; 1786 assert(ss == es); 1787 1788 const dss = deserialize!(TestSerializer, typeof(s))(ss); 1789 assert(dss == s); 1790 1791 static struct T { @asArray S!(int, string) g; } 1792 enum Tm = T.mangleof; 1793 const t = T(s); 1794 1795 const st = serialize!TestSerializer(t); 1796 const et = "D("~Tm~"){DE("~Sm~",g)(A("~Sm~")[2][AE(i,0)(V(i)(42))AE(i,0)AE(Aya,1)(V(Aya)(hello))AE(Aya,1)]A("~Sm~"))DE("~Sm~",g)}D("~Tm~")"; 1797 assert(st == et); 1798 1799 const dst = deserialize!(TestSerializer, typeof(t))(st); 1800 assert(dst == t); 1801 } 1802 1803 unittest { // named tuple serialization 1804 import std.typecons : tuple; 1805 1806 static struct I { 1807 int i; 1808 } 1809 1810 static struct S { 1811 int x; 1812 string s_; 1813 } 1814 1815 static struct T { 1816 @asArray 1817 typeof(tuple!(FieldNameTuple!I)(I.init.tupleof)) tuple1AsArray; 1818 1819 @name(fullyQualifiedName!I) 1820 typeof(tuple!(FieldNameTuple!I)(I.init.tupleof)) tuple1AsDictionary; 1821 1822 @asArray 1823 typeof(tuple!(FieldNameTuple!S)(S.init.tupleof)) tuple2AsArray; 1824 1825 @name(fullyQualifiedName!S) 1826 typeof(tuple!(FieldNameTuple!S)(S.init.tupleof)) tuple2AsDictionary; 1827 } 1828 1829 const i = I(42); 1830 const s = S(42, "hello"); 1831 const T t = { i.tupleof, i.tupleof, s.tupleof, s.tupleof }; 1832 1833 const st = serialize!TestSerializer(t); 1834 1835 enum Tm = T.mangleof; 1836 enum TuIm = typeof(T.tuple1AsArray).mangleof; 1837 enum TuSm = typeof(T.tuple2AsArray).mangleof; 1838 1839 const et = 1840 "D("~Tm~")"~ 1841 "{"~ 1842 "DE("~TuIm~",tuple1AsArray)"~ 1843 "("~ 1844 "V(i)(42)"~ 1845 ")"~ 1846 "DE("~TuIm~",tuple1AsArray)"~ 1847 "DE("~TuIm~","~fullyQualifiedName!I~")"~ 1848 "("~ 1849 "D("~TuIm~")"~ 1850 "{"~ 1851 "DE(i,i)"~ 1852 "("~ 1853 "V(i)(42)"~ 1854 ")"~ 1855 "DE(i,i)"~ 1856 "}"~ 1857 "D("~TuIm~")"~ 1858 ")"~ 1859 "DE("~TuIm~","~fullyQualifiedName!I~")"~ 1860 "DE("~TuSm~",tuple2AsArray)"~ 1861 "("~ 1862 "A("~TuSm~")[2]"~ 1863 "["~ 1864 "AE(i,0)"~ 1865 "("~ 1866 "V(i)(42)"~ 1867 ")"~ 1868 "AE(i,0)"~ 1869 "AE(Aya,1)"~ 1870 "("~ 1871 "V(Aya)(hello)"~ 1872 ")"~ 1873 "AE(Aya,1)"~ 1874 "]"~ 1875 "A("~TuSm~")"~ 1876 ")"~ 1877 "DE("~TuSm~",tuple2AsArray)"~ 1878 "DE("~TuSm~","~fullyQualifiedName!S~")"~ 1879 "("~ 1880 "D("~TuSm~")"~ 1881 "{"~ 1882 "DE(i,x)"~ 1883 "("~ 1884 "V(i)(42)"~ 1885 ")"~ 1886 "DE(i,x)"~ 1887 "DE(Aya,s)"~ 1888 "("~ 1889 "V(Aya)(hello)"~ 1890 ")"~ 1891 "DE(Aya,s)"~ 1892 "}"~ 1893 "D("~TuSm~")"~ 1894 ")"~ 1895 "DE("~TuSm~","~fullyQualifiedName!S~")"~ 1896 "}"~ 1897 "D("~Tm~")"; 1898 assert(st == et); 1899 1900 const dst = deserialize!(TestSerializer, typeof(t))(st); 1901 assert(dst == t); 1902 } 1903 1904 unittest { // testing the various UDAs 1905 enum E { hello, world } 1906 enum Em = E.mangleof; 1907 static struct S { 1908 @byName E e; 1909 @ignore int i; 1910 @optional float f; 1911 } 1912 enum Sm = S.mangleof; 1913 auto s = S(E.world, 42, 1.0f); 1914 assert(serialize!TestSerializer(s) == 1915 "D("~Sm~"){DE("~Em~",e)(V(Aya)(world))DE("~Em~",e)DE(f,f)(V(f)(1))DE(f,f)}D("~Sm~")"); 1916 } 1917 1918 unittest { // custom serialization support 1919 // iso-ext 1920 import std.datetime; 1921 auto t = TimeOfDay(6, 31, 23); 1922 assert(serialize!TestSerializer(t) == "V(Aya)(06:31:23)"); 1923 auto d = Date(1964, 1, 23); 1924 assert(serialize!TestSerializer(d) == "V(Aya)(1964-01-23)"); 1925 auto dt = DateTime(d, t); 1926 assert(serialize!TestSerializer(dt) == "V(Aya)(1964-01-23T06:31:23)"); 1927 auto st = SysTime(dt, UTC()); 1928 assert(serialize!TestSerializer(st) == "V(Aya)(1964-01-23T06:31:23Z)"); 1929 } 1930 1931 @safe unittest { // custom serialization support 1932 // string 1933 static struct S1 { int i; string toString() const @safe { return "hello"; } static S1 fromString(string) @safe { return S1.init; } } 1934 static struct S2 { int i; string toString() const { return "hello"; } } 1935 enum S2m = S2.mangleof; 1936 static struct S3 { int i; static S3 fromString(string) { return S3.init; } } 1937 enum S3m = S3.mangleof; 1938 assert(serialize!TestSerializer(S1.init) == "V(Aya)(hello)"); 1939 assert(serialize!TestSerializer(S2.init) == "D("~S2m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~S2m~")"); 1940 assert(serialize!TestSerializer(S3.init) == "D("~S3m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~S3m~")"); 1941 1942 // custom 1943 static struct C1 { int i; float toRepresentation() const @safe { return 1.0f; } static C1 fromRepresentation(float f) @safe { return C1.init; } } 1944 static struct C2 { int i; float toRepresentation() const { return 1.0f; } } 1945 enum C2m = C2.mangleof; 1946 static struct C3 { int i; static C3 fromRepresentation(float f) { return C3.init; } } 1947 enum C3m = C3.mangleof; 1948 assert(serialize!TestSerializer(C1.init) == "V(f)(1)"); 1949 assert(serialize!TestSerializer(C2.init) == "D("~C2m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~C2m~")"); 1950 assert(serialize!TestSerializer(C3.init) == "D("~C3m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~C3m~")"); 1951 } 1952 1953 unittest // Testing corner case: member function returning by ref 1954 { 1955 import dub.internal.vibecompat.data.json; 1956 1957 static struct S 1958 { 1959 int i; 1960 ref int foo() return { return i; } 1961 } 1962 1963 static assert(__traits(compiles, { S().serializeToJson(); })); 1964 static assert(__traits(compiles, { Json().deserializeJson!S(); })); 1965 1966 auto s = S(1); 1967 assert(s.serializeToJson().deserializeJson!S() == s); 1968 } 1969 1970 unittest // Testing corner case: Variadic template constructors and methods 1971 { 1972 import dub.internal.vibecompat.data.json; 1973 1974 static struct S 1975 { 1976 int i; 1977 this(Args...)(Args args) {} 1978 int foo(Args...)(Args args) { return i; } 1979 ref int bar(Args...)(Args args) { return i; } 1980 } 1981 1982 static assert(__traits(compiles, { S().serializeToJson(); })); 1983 static assert(__traits(compiles, { Json().deserializeJson!S(); })); 1984 1985 auto s = S(1); 1986 assert(s.serializeToJson().deserializeJson!S() == s); 1987 } 1988 1989 @safe unittest // Make sure serializing through properties still works 1990 { 1991 import dub.internal.vibecompat.data.json; 1992 1993 static struct S 1994 { 1995 @safe: 1996 public int i; 1997 private int privateJ; 1998 1999 @property int j() @safe { return privateJ; } 2000 @property void j(int j) @safe { privateJ = j; } 2001 } 2002 2003 auto s = S(1, 2); 2004 assert(s.serializeToJson().deserializeJson!S() == s); 2005 } 2006 2007 @safe unittest // Immutable data deserialization 2008 { 2009 import dub.internal.vibecompat.data.json; 2010 2011 static struct S { 2012 int a; 2013 } 2014 static class C { 2015 immutable(S)[] arr; 2016 } 2017 2018 auto c = new C; 2019 c.arr ~= S(10); 2020 auto d = c.serializeToJson().deserializeJson!(immutable C); 2021 static assert(is(typeof(d) == immutable C)); 2022 assert(d.arr == c.arr); 2023 } 2024 2025 unittest { // test BitFlags serialization 2026 import std.typecons : BitFlags; 2027 2028 enum Flag { 2029 a = 1<<0, 2030 b = 1<<1, 2031 c = 1<<2 2032 } 2033 enum Flagm = Flag.mangleof; 2034 2035 alias Flags = BitFlags!Flag; 2036 enum Flagsm = Flags.mangleof; 2037 2038 enum Fi_ser = "A("~Flagsm~")[0][]A("~Flagsm~")"; 2039 assert(serialize!TestSerializer(Flags.init) == Fi_ser); 2040 2041 enum Fac_ser = "A("~Flagsm~")[2][AE("~Flagm~",0)(V(i)(1))AE("~Flagm~",0)AE("~Flagm~",1)(V(i)(4))AE("~Flagm~",1)]A("~Flagsm~")"; 2042 assert(serialize!TestSerializer(Flags(Flag.a, Flag.c)) == Fac_ser); 2043 2044 struct S { @byName Flags f; } 2045 enum Sm = S.mangleof; 2046 enum Sac_ser = "D("~Sm~"){DE("~Flagsm~",f)(A("~Flagsm~")[2][AE("~Flagm~",0)(V(Aya)(a))AE("~Flagm~",0)AE("~Flagm~",1)(V(Aya)(c))AE("~Flagm~",1)]A("~Flagsm~"))DE("~Flagsm~",f)}D("~Sm~")"; 2047 2048 assert(serialize!TestSerializer(S(Flags(Flag.a, Flag.c))) == Sac_ser); 2049 2050 assert(deserialize!(TestSerializer, Flags)(Fi_ser) == Flags.init); 2051 assert(deserialize!(TestSerializer, Flags)(Fac_ser) == Flags(Flag.a, Flag.c)); 2052 assert(deserialize!(TestSerializer, S)(Sac_ser) == S(Flags(Flag.a, Flag.c))); 2053 } 2054 2055 @safe unittest { // issue #1182 2056 struct T { 2057 int x; 2058 string y; 2059 } 2060 struct S { 2061 @asArray T t; 2062 } 2063 2064 auto s = S(T(42, "foo")); 2065 enum Sm = S.mangleof; 2066 enum Tm = T.mangleof; 2067 enum s_ser = "D("~Sm~"){DE("~Tm~",t)(A("~Tm~")[2][AE(i,0)(V(i)(42))AE(i,0)AE(Aya,1)(V(Aya)(foo))AE(Aya,1)]A("~Tm~"))DE("~Tm~",t)}D("~Sm~")"; 2068 2069 auto serialized = serialize!TestSerializer(s); 2070 assert(serialized == s_ser, serialized); 2071 assert(deserialize!(TestSerializer, S)(serialized) == s); 2072 } 2073 2074 @safe unittest { // issue #1352 - ingore per policy 2075 struct P1 {} 2076 struct P2 {} 2077 2078 struct T { 2079 @ignore int a = 5; 2080 @ignore!P1 @ignore!P2 int b = 6; 2081 @ignore!P1 c = 7; 2082 int d = 8; 2083 } 2084 2085 auto t = T(1, 2, 3, 4); 2086 auto Tm = T.mangleof; 2087 auto t_ser_plain = "D("~Tm~"){DE(i,b)(V(i)(2))DE(i,b)DE(i,c)(V(i)(3))DE(i,c)DE(i,d)(V(i)(4))DE(i,d)}D("~Tm~")"; 2088 auto t_ser_p1 = "D("~Tm~"){DE(i,d)(V(i)(4))DE(i,d)}D("~Tm~")"; 2089 auto t_ser_p2 = "D("~Tm~"){DE(i,c)(V(i)(3))DE(i,c)DE(i,d)(V(i)(4))DE(i,d)}D("~Tm~")"; 2090 2091 { 2092 auto serialized_plain = serialize!TestSerializer(t); 2093 assert(serialized_plain == t_ser_plain); 2094 assert(deserialize!(TestSerializer, T)(serialized_plain) == T(5, 2, 3, 4)); 2095 } 2096 2097 { 2098 auto serialized_p1 = serializeWithPolicy!(TestSerializer, P1)(t); 2099 assert(serialized_p1 == t_ser_p1, serialized_p1); 2100 assert(deserializeWithPolicy!(TestSerializer, P1, T)(serialized_p1) == T(5, 6, 7, 4)); 2101 } 2102 2103 { 2104 auto serialized_p2 = serializeWithPolicy!(TestSerializer, P2)(t); 2105 assert(serialized_p2 == t_ser_p2); 2106 assert(deserializeWithPolicy!(TestSerializer, P2, T)(serialized_p2) == T(5, 6, 3, 4)); 2107 } 2108 } 2109 2110 unittest { 2111 import std.conv : to; 2112 import std.string : toLower, toUpper; 2113 2114 template P(T) if (is(T == enum)) { 2115 @safe: 2116 static string toRepresentation(T v) { return v.to!string.toLower(); } 2117 static T fromRepresentation(string str) { return str.toUpper().to!T; } 2118 } 2119 2120 2121 enum E { 2122 RED, 2123 GREEN 2124 } 2125 2126 assert(P!E.fromRepresentation("green") == E.GREEN); 2127 static assert(isPolicySerializable!(P, E)); 2128 2129 auto ser_red = "V(Aya)(red)"; 2130 assert(serializeWithPolicy!(TestSerializer, P)(E.RED) == ser_red, serializeWithPolicy!(TestSerializer, P)(E.RED)); 2131 assert(deserializeWithPolicy!(TestSerializer, P, E)(ser_red) == E.RED); 2132 2133 import dub.internal.vibecompat.data.json : Json, JsonSerializer; 2134 assert(serializeWithPolicy!(JsonSerializer, P)(E.RED) == Json("red")); 2135 } 2136 2137 unittest { 2138 static struct R { int y; } 2139 static struct Custom { 2140 @safe: 2141 int x; 2142 R toRepresentation() const { return R(x); } 2143 static Custom fromRepresentation(R r) { return Custom(r.y); } 2144 } 2145 2146 auto c = Custom(42); 2147 auto Rn = R.mangleof; 2148 auto ser = serialize!TestSerializer(c); 2149 assert(ser == "D("~Rn~"){DE(i,y)(V(i)(42))DE(i,y)}D("~Rn~")"); 2150 auto deser = deserialize!(TestSerializer, Custom)(ser); 2151 assert(deser.x == 42); 2152 } 2153 2154 unittest { 2155 import std.typecons : Typedef; 2156 alias T = Typedef!int; 2157 auto ser = serialize!TestSerializer(T(42)); 2158 assert(ser == "V(i)(42)", ser); 2159 auto deser = deserialize!(TestSerializer, T)(ser); 2160 assert(deser == 42); 2161 } 2162 2163 @safe unittest { 2164 static struct Foo { Foo[] foos; } 2165 Foo f; 2166 string ser = serialize!TestSerializer(f); 2167 assert(deserialize!(TestSerializer, Foo)(ser) == f); 2168 } 2169 2170 @system unittest { 2171 static struct SystemSerializer { 2172 TestSerializer ser; 2173 alias ser this; 2174 this(string s) { ser.result = s; } 2175 T readValue(Traits, T)() @system { return ser.readValue!(Traits, T); } 2176 void writeValue(Traits, T)(T value) @system { ser.writeValue!(Traits, T)(value); } 2177 void readDictionary(Traits)(scope void delegate(string) @system entry_callback) { return ser.readDictionary!Traits((s) @trusted { entry_callback(s); }); } 2178 void readArray(Traits)(scope void delegate(size_t) @system size_callback, scope void delegate() @system entry_callback) { ser.readArray!Traits((s) @trusted { size_callback(s); }, () @trusted { entry_callback(); }); } 2179 } 2180 2181 static struct Bar { Bar[] foos; int i; } 2182 Bar f; 2183 string ser = serialize!SystemSerializer(f); 2184 assert(deserialize!(SystemSerializer, Bar)(ser) == f); 2185 } 2186 2187 @safe unittest { 2188 static struct S { @name("+foo") int bar; } 2189 auto Sn = S.mangleof; 2190 auto s = S(42); 2191 string ser = serialize!TestSerializer(s); 2192 assert(ser == "D("~Sn~"){DE(i,+foo)(V(i)(42))DE(i,+foo)}D("~Sn~")", ser); 2193 auto deser = deserialize!(TestSerializer, S)(ser); 2194 assert(deser.bar == 42); 2195 } 2196 2197 @safe unittest { 2198 static struct S { int bar_; } 2199 auto Sn = S.mangleof; 2200 auto s = S(42); 2201 string ser = serialize!TestSerializer(s); 2202 assert(ser == "D("~Sn~"){DE(i,bar)(V(i)(42))DE(i,bar)}D("~Sn~")", ser); 2203 auto deser = deserialize!(TestSerializer, S)(ser); 2204 assert(deser.bar_ == 42); 2205 } 2206 2207 @safe unittest { // issue 1941 2208 static struct Bar { Bar[] foos; int i; } 2209 Bar b1 = {[{null, 2}], 1}; 2210 auto s = serialize!TestSerializer(b1); 2211 auto b = deserialize!(TestSerializer, Bar)(s); 2212 assert(b.i == 1); 2213 assert(b.foos.length == 1); 2214 assert(b.foos[0].i == 2); 2215 } 2216 2217 unittest { // issue 1991 - @system property getters/setters does not compile 2218 static class A { 2219 @safe: 2220 @property @name("foo") { 2221 string fooString() const { return "a"; } 2222 void fooString(string a) { } 2223 } 2224 } 2225 2226 auto a1 = new A; 2227 auto b = serialize!TestSerializer(a1); 2228 auto a2 = deserialize!(TestSerializer, A)(b); 2229 } 2230 2231 unittest { // issue #2110 - single-element tuples 2232 static struct F { int field; } 2233 2234 { 2235 static struct S { typeof(F.init.tupleof) fields; } 2236 auto b = serialize!TestSerializer(S(42)); 2237 auto a = deserialize!(TestSerializer, S)(b); 2238 assert(a.fields[0] == 42); 2239 } 2240 2241 { 2242 static struct T { @asArray typeof(F.init.tupleof) fields; } 2243 auto b = serialize!TestSerializer(T(42)); 2244 auto a = deserialize!(TestSerializer, T)(b); 2245 assert(a.fields[0] == 42); 2246 } 2247 } 2248 2249 @safe unittest { 2250 import std.typecons : Nullable; 2251 2252 struct S { 2253 @embedNullable Nullable!int x; 2254 @embedNullable Nullable!string s; 2255 } 2256 2257 enum Sn = S.mangleof; 2258 2259 auto s = S(Nullable!int(3), Nullable!string.init); 2260 auto expected = "D("~Sn~"){DE(i,x)(V(i)(3))DE(i,x)}D("~Sn~")"; 2261 2262 assert(serialize!TestSerializer(s) == expected, serialize!TestSerializer(s)); 2263 assert(deserialize!(TestSerializer, S)(expected) == s); 2264 2265 s.s = "hello"; 2266 expected = "D("~Sn~"){DE(i,x)(V(i)(3))DE(i,x)DE(Aya,s)(V(Aya)(hello))DE(Aya,s)}D("~Sn~")"; 2267 assert(serialize!TestSerializer(s) == expected, serialize!TestSerializer(s)); 2268 assert(deserialize!(TestSerializer, S)(expected) == s); 2269 2270 s.x.nullify(); 2271 expected = "D("~Sn~"){DE(Aya,s)(V(Aya)(hello))DE(Aya,s)}D("~Sn~")"; 2272 assert(serialize!TestSerializer(s) == expected); 2273 assert(deserialize!(TestSerializer, S)(expected) == s); 2274 2275 s.s.nullify(); 2276 expected = "D("~Sn~"){}D("~Sn~")"; 2277 assert(serialize!TestSerializer(s) == expected); 2278 assert(deserialize!(TestSerializer, S)(expected) == s); 2279 } 2280 2281 unittest { 2282 import std.conv : ConvException; 2283 import std.exception : assertThrown, assertNotThrown; 2284 2285 enum Testable : string { 2286 foo = "foo", 2287 bar = "bar", 2288 baz = "bar" 2289 } 2290 2291 Testable deserializeString(string value) { 2292 return deserialize!(TestSerializer, Testable)("V(Aya)(" ~ value ~ ")"); 2293 } 2294 2295 foreach (string val; ["foo", "bar"]) 2296 assert(deserializeString(val) == val.to!Testable); 2297 2298 assertThrown!ConvException(deserializeString("foobar")); 2299 } 2300 2301 unittest { 2302 import std.conv : ConvException; 2303 import std.exception : assertThrown, assertNotThrown; 2304 2305 enum Foo { 2306 foobar 2307 } 2308 2309 struct Testable { 2310 @byName 2311 Foo bar; 2312 } 2313 2314 void deserializeString(string value) { 2315 auto d = "D(" ~ Testable.mangleof ~ ")"; 2316 auto de = "DE(" ~ Foo.mangleof ~ ",bar)"; 2317 deserialize!(TestSerializer, Testable)(d ~ "{" ~ de ~ "(V(Aya)(" ~ value ~ "))" ~ de ~ "}" ~ d); 2318 } 2319 2320 assertNotThrown(deserializeString("foobar")); 2321 assertThrown!ConvException(deserializeString("baz")); 2322 } 2323 2324 unittest { 2325 @byName 2326 enum Foo { 2327 foobar 2328 } 2329 2330 enum deserialized = Foo.foobar; 2331 enum serialized = "V(Aya)(foobar)"; 2332 2333 assert(serialize!TestSerializer(deserialized) == serialized); 2334 assert(deserialize!(TestSerializer, Foo)(serialized) == deserialized); 2335 } 2336 2337 unittest { // bit flag enums 2338 enum Foo { 2339 a = 1 << 0, 2340 b = 1 << 1 2341 } 2342 2343 const expected = "V(i)(3)"; 2344 assert(serialize!TestSerializer(Foo.a | Foo.b) == expected, serialize!TestSerializer(Foo.a | Foo.b)); 2345 assert(deserialize!(TestSerializer, Foo)(expected) == (Foo.a | Foo.b)); 2346 }