1 /** SDLang data format serialization support for vibe.data.serialization. 2 */ 3 module vibe.data.sdl; 4 5 import vibe.data.serialization; 6 import vibe.data.json; 7 import sdlang; 8 import std.datetime : Date, DateTime, SysTime, TimeOfDay, UTC; 9 import std.meta : allSatisfy, staticIndexOf; 10 import std.traits : Unqual, ValueType, hasUDA, isNumeric, isBoolean, isArray, isAssociativeArray; 11 import core.time : Duration, days, hours, seconds; 12 13 14 /// 15 unittest { 16 import std.conv : to; 17 18 static struct S { 19 bool b; 20 int i; 21 float f; 22 double d; 23 string str; 24 ubyte[] data; 25 SysTime sysTime; 26 DateTimeFrac dateTime; 27 Date date; 28 Duration dur; 29 } 30 31 S s = { 32 b : true, 33 i: 12, 34 f: 0.5f, 35 d: 0.5, 36 str: "foo", 37 data: [1, 2, 3], 38 sysTime: SysTime(DateTime(Date(2016, 12, 10), TimeOfDay(9, 30, 23)), UTC()), 39 dateTime: DateTimeFrac(DateTime(Date(2016, 12, 10), TimeOfDay(9, 30, 23))), 40 date: Date(2016, 12, 10), 41 dur: 2.days + 2.hours + 2.seconds 42 }; 43 44 auto res = serializeSDLang(s); 45 assert(res.toSDLDocument() == "b true\ni 12\nf 0.5F\nd 0.5D\nstr \"foo\"\ndata [AQID]\n" 46 ~ "sysTime 2016/12/10 09:30:23-UTC\ndateTime 2016/12/10 09:30:23\ndate 2016/12/10\n" 47 ~ "dur 2d:02:00:02\n", [res.toSDLDocument()].to!string); 48 49 auto t = deserializeSDLang!S(res); 50 assert(s == t); 51 } 52 53 54 /// 55 unittest { 56 static struct U { 57 @sdlAttribute int att1; 58 string content1; 59 } 60 61 static struct T { 62 @sdlAttribute int att1; 63 @sdlAttribute string att2; 64 @sdlValue string content; 65 } 66 67 static struct S { 68 string[string] dict; 69 U[] arr; 70 T[] arr1; 71 @sdlSingle T[] arr2; 72 int[] iarr; 73 } 74 75 S s = { 76 dict : ["a": "foo", "b": "bar"], 77 arr : [U(1, "x"), U(2, "y")], 78 arr1 : [T(1, "a", "x"), T(2, "b", "y")], 79 arr2 : [T(1, "a", "x"), T(2, "b", "y")], 80 iarr : [1, 2, 3] 81 }; 82 83 auto res = serializeSDLang(s); 84 assert(res.toSDLDocument() == 85 `dict { 86 "b" "bar" 87 "a" "foo" 88 } 89 arr { 90 entry att1=1 { 91 content1 "x" 92 } 93 entry att1=2 { 94 content1 "y" 95 } 96 } 97 arr1 { 98 entry "x" att1=1 att2="a" 99 entry "y" att1=2 att2="b" 100 } 101 arr2 "x" att1=1 att2="a" 102 arr2 "y" att1=2 att2="b" 103 iarr 1 2 3 104 `, res.toSDLDocument()); 105 106 S t = deserialize!(SDLangSerializer, S)(res); 107 import std.conv : to; 108 assert(s == t, t.to!string); 109 } 110 111 /** Serializes a value as an SDLang document. 112 */ 113 Tag serializeSDLang(T, alias Policy = DefaultPolicy)(T value) 114 { 115 return serializeWithPolicy!(SDLangSerializer, Policy)(value, new Tag(null, null)); 116 } 117 118 /** Deseriailzes a value from an SDLang document. 119 */ 120 T deserializeSDLang(T, alias Policy = DefaultPolicy)(Tag sdl) 121 { 122 return deserializeWithPolicy!(SDLangSerializer, Policy, T)(sdl); 123 } 124 125 126 /// 127 struct SDLangSerializer { 128 @trusted: 129 enum isSDLBasicType(T) = 130 isNumeric!T || 131 isBoolean!T || 132 is(T == string) || 133 is(T == ubyte[]) || 134 is(T == SysTime) || 135 is(T == DateTime) || 136 is(T == DateTimeFrac) || 137 is(T == Date) || 138 is(T == Duration) || 139 is(T == typeof(null)) || 140 isSDLSerializable!T; 141 142 enum isSupportedValueType(T) = isSDLBasicType!T || is(T == Tag); 143 144 private enum Loc { subNodes, attribute, values } 145 146 private static struct StackEntry { 147 Tag tag; 148 Attribute attribute; 149 Loc loc; 150 size_t valIdx; 151 bool hasIdentKeys, isArrayEntry; 152 string singleArrayName; 153 } 154 155 private { 156 StackEntry[] m_stack; 157 } 158 159 this(Tag data) { pushTag(data); } 160 161 @disable this(this); 162 163 // 164 // serialization 165 // 166 Tag getSerializedResult() { return m_stack[0].tag; } 167 168 void beginWriteDictionary(Traits)() 169 { 170 current.hasIdentKeys = !isAssociativeArray!(Traits.Type); 171 } 172 void endWriteDictionary(Traits)() {} 173 void beginWriteDictionaryEntry(ElementTraits)(string name) { 174 assert(m_stack.length > 0); 175 assert(isAssociativeArray!(ElementTraits.ContainerType) || name.length > 0); 176 static if (containsValue!(SDLSingleAttribute, ElementTraits.Attributes)) { 177 current.singleArrayName = name; 178 current.loc = Loc.subNodes; 179 } else static if (isSDLBasicType!(ElementTraits.Type) && containsValue!(SDLAttributeAttribute, ElementTraits.Attributes)) { 180 current.attribute = new Attribute(null, name, Value(null)); 181 current.tag.add(current.attribute); 182 current.loc = Loc.attribute; 183 } else static if (isSDLBasicType!(ElementTraits.Type) && containsValue!(SDLValueAttribute, ElementTraits.Attributes)) { 184 current.loc = Loc.values; 185 } else { 186 if (current.hasIdentKeys) pushTag(name); 187 else pushTag(null, () @trusted { return Value(name); } ()); 188 current.singleArrayName = "entry"; 189 current.loc = Loc.values; 190 } 191 } 192 void endWriteDictionaryEntry(ElementTraits)(string name) { 193 static if (containsValue!(SDLSingleAttribute, ElementTraits.Attributes)) {} 194 else static if (isSDLBasicType!(ElementTraits.Type) && containsValue!(SDLAttributeAttribute, ElementTraits.Attributes)) {} 195 else static if (isSDLBasicType!(ElementTraits.Type) && containsValue!(SDLValueAttribute, ElementTraits.Attributes)) {} 196 else pop(); 197 } 198 199 200 void beginWriteArray(Traits)(size_t) 201 if (isValueArray!(Traits.Type)) 202 { 203 current.loc = Loc.values; 204 } 205 void endWriteArray(Traits)() if (isValueArray!(Traits.Type)) {} 206 void beginWriteArray(Traits)(size_t) if (!isValueArray!(Traits.Type)) 207 { 208 current.loc = Loc.subNodes; 209 } 210 void endWriteArray(Traits)() if (!isValueArray!(Traits.Type)) {} 211 void beginWriteArrayEntry(ElementTraits)(size_t idx) 212 { 213 if (current.loc == Loc.subNodes) { 214 static if (containsValue!(SDLSingleAttribute, ElementTraits.ContainerAttributes)) { 215 if (idx > 0 && m_stack.length > 1) { 216 auto name = current.tag.name; 217 assert(name.length > 0); 218 pop(); 219 pushTag(name); 220 return; 221 } 222 } 223 224 assert(current.singleArrayName.length > 0); 225 pushTag(current.singleArrayName); 226 current.isArrayEntry = true; 227 } 228 } 229 void endWriteArrayEntry(ElementTraits)(size_t) 230 { 231 if (current.isArrayEntry) pop(); 232 } 233 234 void writeValue(Traits, T)(in T value) @trusted 235 if (!is(T == Tag)) 236 { 237 import std.traits : isIntegral; 238 239 static if (isSDLSerializable!T) writeValue(value.toSDL()); 240 else { 241 Value val; 242 static if (is(T == DateTime)) val = DateTimeFrac(value); 243 else { 244 Unqual!T uval; 245 static if (is(typeof(uval = value))) uval = value; 246 else uval = value.dup; 247 static if (isIntegral!T) { 248 static if (T.sizeof <= int.sizeof) val = cast(int)uval; 249 else val = uval; 250 } else val = uval; 251 } 252 253 final switch (current.loc) { 254 case Loc.attribute: current.attribute.value = val; break; 255 case Loc.values: current.tag.add(val); break; 256 case Loc.subNodes: current.tag.add(new Tag(null, null, [val])); break; 257 } 258 } 259 } 260 261 void writeValue(Traits, T)(Tag value) if (is(T == Tag)) { current.tag.add(value); } 262 void writeValue(Traits, T)(in Json Tag) if (is(T == Tag)) { current.tag.add(value.clone); } 263 264 // 265 // deserialization 266 // 267 void readDictionary(Traits)(scope void delegate(string) @safe field_handler) 268 if (isAssociativeArray!(Traits.Type)) 269 { 270 foreach (st; current.tag.tags) { 271 pushTag(st); 272 current.valIdx = 1; 273 current.loc = Loc.values; 274 auto n = st.values[0].get!string; 275 field_handler(n); 276 pop(); 277 } 278 } 279 280 void readDictionary(Traits)(scope void delegate(string) @safe field_handler) 281 if (!isAssociativeArray!(Traits.Type)) 282 { 283 import std.meta : AliasSeq; 284 import std.traits : FieldNameTuple, hasUDA; 285 foreach (att; current.tag.attributes) { 286 current.loc = Loc.attribute; 287 current.attribute = att; 288 field_handler(att.name); 289 } 290 size_t first_idx = 0; // FIXME: 1 for non-ident keys 291 foreach (st; current.tag.tags) { 292 pushTag(st); 293 current.loc = Loc.values; 294 current.valIdx = 0; 295 field_handler(st.name); 296 pop(); 297 } 298 static if (is(Traits.Type == struct) || is(Traits.Type == class)) { 299 current.valIdx = 0; 300 foreach (fname; FieldNameTuple!(Traits.Type)) { 301 alias F = AliasSeq!(__traits(getMember, Traits.Type, fname)); 302 static if (hasUDAValue!(F[0], SDLValueAttribute)) { 303 current.loc = Loc.values; 304 if (current.valIdx >= current.tag.values.length) { 305 import std.format : format; 306 throw new Exception(format("Line %s: Missing value number %s (%s) for '%s'.", current.tag.location.line+1, current.valIdx+1, fname, current.tag.name)); // TODO: show line number 307 } 308 309 field_handler(__traits(identifier, F[0])); 310 current.valIdx++; 311 } 312 } 313 } 314 } 315 316 void beginReadDictionaryEntry(Traits)(string name) 317 { 318 } 319 320 void endReadDictionaryEntry(Traits)(string name) 321 { 322 } 323 324 void readArray(Traits)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback) 325 if (containsValue!(SDLSingleAttribute, Traits.Attributes)) 326 { 327 // FIXME: ensure that this is only called once and not for each array entry tag 328 auto name = current.tag.name; 329 foreach (t; current.tag.parent.tags) { 330 if (t.name == name) { 331 pushTag(t); 332 entry_callback(); 333 pop(); 334 } 335 } 336 } 337 338 void readArray(Traits)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback) 339 if (!containsValue!(SDLSingleAttribute, Traits.Attributes) && isValueArray!(Traits.Type)) 340 { 341 current.loc = Loc.values; 342 current.valIdx = 0; 343 size_callback(current.tag.values.length); 344 while (current.valIdx < current.tag.values.length) { 345 entry_callback(); 346 current.valIdx++; 347 } 348 } 349 350 void readArray(Traits)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback) 351 if (!containsValue!(SDLSingleAttribute, Traits.Attributes) && !isValueArray!(Traits.Type)) 352 { 353 size_callback(current.tag.tags.length); 354 foreach (st; current.tag.tags) { 355 pushTag(st); 356 entry_callback(); 357 pop(); 358 } 359 } 360 361 void beginReadArrayEntry(Traits)(size_t idx) 362 { 363 } 364 365 void endReadArrayEntry(Traits)(size_t idx) 366 { 367 } 368 369 T readValue(Traits, T)() 370 { 371 import std.conv : to; 372 import std.traits : isIntegral; 373 374 auto val = getCurrentValue(); 375 static if (isIntegral!T) { 376 static if (T.sizeof <= int.sizeof) return val.get!int.to!T; 377 else return val.get!long; 378 } else return val.get!T; 379 } 380 381 bool tryReadNull(Traits)() 382 { 383 auto val = getCurrentValue(); 384 if (!val.hasValue) return false; 385 return val.peek!(typeof(null)) !is null; 386 } 387 388 private @property ref inout(StackEntry) current() inout { return m_stack[$-1]; } 389 390 private void pushTag(string name) { pushTag(new Tag(current.tag, null, name)); } 391 private void pushTag(string name, Value value) { pushTag(new Tag(current.tag, null, name, [value])); } 392 private void pushTag(Tag tag) 393 { 394 StackEntry se; 395 se.tag = tag; 396 se.valIdx = tag.values.length; 397 m_stack ~= se; 398 } 399 400 private void pushAttribute(string name) 401 { 402 StackEntry se; 403 se.attribute = new Attribute(null, name, Value.init); 404 se.tag = current.tag; 405 se.loc = Loc.attribute; 406 current.tag.add(se.attribute); 407 m_stack ~= se; 408 } 409 410 private void pop() 411 { 412 assert(m_stack.length > 1, "Popping last element!?"); 413 m_stack.length--; 414 m_stack.assumeSafeAppend(); 415 } 416 417 418 private Value getCurrentValue() 419 { 420 final switch (current.loc) { 421 case Loc.attribute: return current.attribute.value; 422 case Loc.values: 423 if (current.valIdx < current.tag.values.length) 424 return current.tag.values[current.valIdx]; 425 return Value.init; 426 case Loc.subNodes: 427 if (current.tag.values.length > 0) 428 return current.tag.values[0]; 429 return Value.init; 430 } 431 } 432 433 private template isValueDictionary(T) { 434 static if (isAssociativeArray!T) 435 enum isValueDictionary = isSDLBasicType!(ValueType!T); 436 else static if (is(T == struct) || is(T == class)) 437 enum isValueDictionary = anySatisfy!(isValueField, T.tupleof) && allSatisfy!(isValueOrAttributeField, T.tupleof); 438 else enum isValueDictionary = false; 439 } 440 441 private template isValueArray(T) { 442 static if (isArray!T) 443 enum isValueArray = isSDLBasicType!(typeof(T.init[0])); 444 else enum isValueArray = false; 445 } 446 } 447 448 /** Forces a value to be serialized as an SDL attribute. 449 450 This can be applied to plain values in aggregate members and will cause 451 the vaule to be seriailzed as an attribute of the parent tag instead of 452 as a child tag. 453 */ 454 @property SDLAttributeAttribute sdlAttribute() { return SDLAttributeAttribute.init; } 455 456 /// 457 unittest { 458 struct S { 459 int foo; 460 @sdlAttribute int bar; 461 } 462 463 struct T { 464 S s; 465 } 466 467 assert( 468 serializeSDLang(T(S(1, 2))).toSDLDocument() == 469 "s bar=2 {\n" ~ 470 "\tfoo 1\n" ~ 471 "}\n" 472 ); 473 } 474 475 476 /** Forces a value to be serialized as an SDL value. 477 478 This can be applied to plain values in aggregate members and will cause 479 the vaule to be seriailzed as a value of the parent tag instead of 480 as a child tag. 481 */ 482 @property SDLValueAttribute sdlValue() { return SDLValueAttribute.init; } 483 484 /// 485 unittest { 486 struct S { 487 int foo; 488 @sdlValue int bar; 489 } 490 491 struct T { 492 S s; 493 } 494 495 assert( 496 serializeSDLang(T(S(1, 2))).toSDLDocument() == 497 "s 2 {\n" ~ 498 "\tfoo 1\n" ~ 499 "}\n" 500 ); 501 } 502 503 504 /** Causes an array to be serialized as a plain sequence of entry tags instead 505 of wrapping them in a separate parent tag. 506 */ 507 @property SDLSingleAttribute sdlSingle() { return SDLSingleAttribute.init; } 508 509 /// 510 unittest { 511 struct S { 512 @sdlAttribute int foo; 513 @sdlAttribute int bar; 514 } 515 516 struct T { 517 S[] arr1; 518 @sdlSingle S[] arr2; 519 } 520 521 assert( 522 serializeSDLang(T([S(1, 2), S(3, 4)], [S(5, 6), S(7, 8)])).toSDLDocument() == 523 "arr1 {\n" ~ 524 "\tentry foo=1 bar=2\n" ~ 525 "\tentry foo=3 bar=4\n" ~ 526 "}\n" ~ 527 "arr2 foo=5 bar=6\n" ~ 528 "arr2 foo=7 bar=8\n" 529 ); 530 } 531 532 enum isSDLSerializable(T) = is(typeof(T.init.toSDL()) == Tag) && is(typeof(T.fromSDL(new Tag())) == T); 533 enum isValueField(alias F) = hasUDAValue!(F, SDLValueAttribute); 534 enum isValueOrAttributeField(alias F) = hasUDAValue!(F, SDLValueAttribute) || hasUDAValue!(F, SDLAttributeAttribute); 535 536 /// private 537 private struct SDLAttributeAttribute {} 538 /// private 539 private struct SDLSingleAttribute {} 540 /// private 541 private struct SDLValueAttribute {} 542 543 private enum hasUDAValue(alias DECL, T) = containsValue!(T, __traits(getAttributes, DECL)); 544 private template containsValue(T, V...) { 545 static if (V.length > 0) 546 enum containsValue = is(typeof(V[0]) == T) || containsValue!(T, V[1 .. $]); 547 else enum containsValue = false; 548 } 549 550 unittest { 551 struct S { 552 @sdlSingle int single; 553 @sdlValue int value; 554 @sdlAttribute int attribute; 555 } 556 557 static assert(hasUDAValue!(S.single, SDLSingleAttribute)); 558 static assert(!hasUDAValue!(S.single, SDLValueAttribute)); 559 static assert(!hasUDAValue!(S.single, SDLAttributeAttribute)); 560 static assert(!hasUDAValue!(S.value, SDLSingleAttribute)); 561 static assert(hasUDAValue!(S.value, SDLValueAttribute)); 562 static assert(!hasUDAValue!(S.value, SDLAttributeAttribute)); 563 static assert(!hasUDAValue!(S.attribute, SDLSingleAttribute)); 564 static assert(!hasUDAValue!(S.attribute, SDLValueAttribute)); 565 static assert(hasUDAValue!(S.attribute, SDLAttributeAttribute)); 566 } 567 568 unittest { 569 struct S { 570 short s; 571 long l; 572 int i; 573 float f; 574 double d; 575 } 576 auto s = S(-30000, -80000000000, -2000000000, 0.5f, 0.5); 577 Tag serialized = s.serializeSDLang(); 578 assert(serialized.toSDLDocument() == "s -30000\nl -80000000000L\ni -2000000000\nf 0.5F\nd 0.5D\n", serialized.toSDLDocument()); 579 assert(serialized.deserializeSDLang!S() == s); 580 }