1 module text.xml.EncodeTest; 2 3 import boilerplate; 4 import dshould; 5 import dxml.writer; 6 import std.array; 7 import std.datetime; 8 import std.typecons; 9 import sumtype : match, SumType; 10 import text.xml.Encode; 11 import text.xml.Tree; 12 import text.xml.Writer; 13 import text.xml.Xml; 14 15 @("fields tagged as Element are encoded as XML elements") 16 unittest 17 { 18 const expected = 19 `<root>` ~ 20 `<IntValueElement>23</IntValueElement>` ~ 21 `<StringValueElement>FOO</StringValueElement>` ~ 22 `<BoolValueElement>true</BoolValueElement>` ~ 23 `<NestedElement>` ~ 24 `<Element>BAR</Element>` ~ 25 `</NestedElement>` ~ 26 `<ArrayElement>1</ArrayElement>` ~ 27 `<ArrayElement>2</ArrayElement>` ~ 28 `<ArrayElement>3</ArrayElement>` ~ 29 `<DateElement>2000-01-02</DateElement>` ~ 30 `<SysTimeElement>2000-01-02T10:00:00Z</SysTimeElement>` ~ 31 `<ContentElement attribute="hello">World</ContentElement>` ~ 32 `</root>` 33 ; 34 35 // given 36 const value = (){ 37 import text.time.Convert : Convert; 38 39 with (Value.Builder()) 40 { 41 intValue = 23; 42 stringValue = "FOO"; 43 boolValue = true; 44 nestedValue = NestedValue("BAR"); 45 arrayValue = [1, 2, 3]; 46 dateValue = Date(2000, 1, 2); 47 sysTimeValue = SysTime.fromISOExtString("2000-01-02T10:00:00Z"); 48 contentValue = ContentValue("hello", "World"); 49 return value; 50 } 51 }(); 52 53 // when 54 auto text = encode(value); 55 56 // then 57 text.should.equal(expected); 58 } 59 60 @("fields tagged as Attribute are encoded as XML attributes") 61 unittest 62 { 63 const expected = `<root intAttribute="23"/>`; 64 65 // given 66 const valueWithAttribute = ValueWithAttribute(23); 67 68 // when 69 auto text = encode(valueWithAttribute); 70 71 // then 72 text.should.equal(expected); 73 } 74 75 @("custom encoders are used on fields") 76 unittest 77 { 78 // given 79 const value = ValueWithEncoders("bla", "bla"); 80 81 // when 82 auto text = encode(value); 83 84 // then 85 const expected = `<root asFoo="foo"><asBar>bar</asBar></root>`; 86 87 text.should.equal(expected); 88 } 89 90 @("custom encoders are used on types") 91 unittest 92 { 93 @(Xml.Element("root")) 94 struct Value 95 { 96 @(Xml.Element("foo")) 97 EncodeNodeTestType foo; 98 99 @(Xml.Attribute("bar")) 100 EncodeAttributeTestType bar; 101 } 102 103 // given 104 const value = Value(EncodeNodeTestType(), EncodeAttributeTestType()); 105 106 // when 107 auto text = .encode(value); 108 109 // then 110 const expected = `<root bar="123"><foo>123</foo></root>`; 111 112 text.should.equal(expected); 113 } 114 115 @("custom encoder on Nullable element") 116 unittest 117 { 118 @(Xml.Element("root")) 119 struct Value 120 { 121 @(Xml.Element("foo")) 122 @(Xml.Encode!encodeNodeTestType) 123 Nullable!EncodeNodeTestType foo; 124 } 125 126 // given 127 const value = Value(Nullable!EncodeNodeTestType()); 128 129 // when 130 const text = .encode(value); 131 132 // then 133 const expected = `<root/>`; 134 135 text.should.equal(expected); 136 } 137 138 @("fields with characters requiring predefined entities") 139 unittest 140 { 141 @(Xml.Element("root")) 142 struct Value 143 { 144 @(Xml.Attribute("foo")) 145 string foo; 146 147 @(Xml.Element("bar")) 148 string bar; 149 } 150 151 // given 152 enum invalidInAttr = `<&"`; 153 enum invalidInText = `<&]]>`; 154 const value = Value(invalidInAttr, invalidInText); 155 156 // when 157 auto text = .encode(value); 158 159 // then 160 const expected = `<root foo="<&""><bar><&]]></bar></root>`; 161 162 text.should.equal(expected); 163 } 164 165 @("regression: encodes optional elements with arrays") 166 unittest 167 { 168 struct Nested 169 { 170 @(Xml.Element("item")) 171 string[] items; 172 } 173 174 @(Xml.Element("root")) 175 struct Root 176 { 177 @(Xml.Element("foo")) 178 Nullable!Nested nested; 179 } 180 181 // given 182 const root = Root(Nullable!Nested(Nested(["foo", "bar"]))); 183 184 // when 185 const text = root.encode; 186 187 // then 188 text.should.equal(`<root><foo><item>foo</item><item>bar</item></foo></root>`); 189 } 190 191 @(Xml.Element("root")) 192 private struct Value 193 { 194 @(Xml.Element("IntValueElement")) 195 public int intValue; 196 197 @(Xml.Element("StringValueElement")) 198 public string stringValue; 199 200 @(Xml.Element("BoolValueElement")) 201 public bool boolValue; 202 203 @(Xml.Element("NestedElement")) 204 public NestedValue nestedValue; 205 206 // Fails to compile when serializing const values 207 @(Xml.Element("ArrayElement")) 208 public int[] arrayValue; 209 210 @(Xml.Element("DateElement")) 211 public Date dateValue; 212 213 @(Xml.Element("SysTimeElement")) 214 public SysTime sysTimeValue; 215 216 @(Xml.Element("ContentElement")) 217 public ContentValue contentValue; 218 219 mixin (GenerateAll); 220 } 221 222 private struct NestedValue 223 { 224 @(Xml.Element("Element")) 225 public string value; 226 227 mixin (GenerateAll); 228 } 229 230 private struct ContentValue 231 { 232 @(Xml.Attribute("attribute")) 233 public string attribute; 234 235 @(Xml.Text) 236 public string content; 237 238 mixin (GenerateAll); 239 } 240 241 @(Xml.Element("root")) 242 private struct ValueWithAttribute 243 { 244 @(Xml.Attribute("intAttribute")) 245 public int value; 246 247 mixin(GenerateAll); 248 } 249 250 @(Xml.Element("root")) 251 private struct ValueWithEncoders 252 { 253 @(Xml.Attribute("asFoo")) 254 @(Xml.Encode!asFoo) 255 public string foo; 256 257 @(Xml.Element("asBar")) 258 @(Xml.Encode!asBar) 259 public string bar; 260 261 static string asFoo(string field) 262 { 263 field.should.equal("bla"); 264 265 return "foo"; 266 } 267 268 static void asBar(ref XMLWriter!(Appender!string) writer, string field) 269 { 270 field.should.equal("bla"); 271 272 writer.writeText("bar", Newline.no); 273 } 274 275 mixin(GenerateThis); 276 } 277 278 package void encodeNodeTestType(ref XMLWriter!(Appender!string) writer, EncodeNodeTestType) 279 { 280 writer.writeText("123", Newline.no); 281 } 282 283 @(Xml.Encode!encodeNodeTestType) 284 package struct EncodeNodeTestType 285 { 286 } 287 288 package string encodeAttributeTestType(EncodeAttributeTestType) 289 { 290 return "123"; 291 } 292 293 @(Xml.Encode!encodeAttributeTestType) 294 package struct EncodeAttributeTestType 295 { 296 } 297 298 @("struct with optional date attribute") 299 unittest 300 { 301 @(Xml.Element("root")) 302 static struct NullableAttributes 303 { 304 @(Xml.Attribute("date")) 305 @(This.Default) 306 Nullable!Date date; 307 308 mixin(GenerateThis); 309 } 310 311 // given 312 const root = NullableAttributes(); 313 314 // when 315 const text = root.encode; 316 317 // then 318 text.should.equal(`<root/>`); 319 } 320 321 @("SumType") 322 unittest 323 { 324 with (SumTypeFixture) 325 { 326 alias Either = SumType!(A, B); 327 328 @(Xml.Element("root")) 329 static struct Struct 330 { 331 Either field; 332 333 mixin(GenerateThis); 334 } 335 336 // given/when/then 337 Struct(Either(A(5))).encode.should.equal(`<root><A a="5"/></root>`); 338 339 Struct(Either(B(3))).encode.should.equal(`<root><B b="3"/></root>`); 340 } 341 } 342 343 @("SumType with arrays") 344 unittest 345 { 346 with (SumTypeFixture) 347 { 348 alias Either = SumType!(A[], B[]); 349 350 @(Xml.Element("root")) 351 static struct Struct 352 { 353 Either field; 354 355 mixin(GenerateThis); 356 } 357 358 // given/when/then 359 Struct(Either([A(5), A(6)])).encode.should.equal(`<root><A a="5"/><A a="6"/></root>`); 360 } 361 } 362 363 private struct SumTypeFixture 364 { 365 @(Xml.Element("A")) 366 static struct A 367 { 368 @(Xml.Attribute("a")) 369 int a; 370 } 371 372 @(Xml.Element("B")) 373 static struct B 374 { 375 @(Xml.Attribute("b")) 376 int b; 377 } 378 } 379 380 @("attribute/element without specified name") 381 unittest 382 { 383 struct Value 384 { 385 @(Xml.Attribute) 386 private int value_; 387 388 mixin(GenerateThis); 389 } 390 391 @(Xml.Element) 392 struct Container 393 { 394 @(Xml.Element) 395 immutable(Value)[] values; 396 397 mixin(GenerateThis); 398 } 399 400 // when 401 const text = Container([Value(1), Value(2), Value(3)]).encode; 402 403 // then 404 text.should.equal(`<Container><Value value="1"/><Value value="2"/><Value value="3"/></Container>`); 405 }