1 module text.json.EncodeTest; 2 3 import boilerplate; 4 import dshould; 5 import std.datetime; 6 import std.json; 7 import text.json.Encode; 8 import text.json.Json; 9 10 // All the tests are executed with both `encodeJson` (encodes to a JSONValue) 11 // and `encode` (encodes to a string). 12 static foreach (bool useEncodeJson; [false, true]) 13 { 14 mixin encodeTests!(useEncodeJson); 15 } 16 17 template encodeTests(bool useEncodeJson) 18 { 19 static if (useEncodeJson) 20 { 21 enum prefix = "encodeJson:"; 22 23 template testEncode(Args...) 24 { 25 JSONValue testEncode(T)(const T value) 26 { 27 return value.encodeJson!(Args); 28 } 29 } 30 } 31 else 32 { 33 enum prefix = "encode:"; 34 35 template testEncode(Args...) 36 { 37 JSONValue testEncode(T)(const T value) 38 { 39 return value.encode!(Args).parseJSON; 40 } 41 } 42 } 43 44 @(prefix ~ "aggregate types are encoded to JSON text") 45 unittest 46 { 47 // given 48 const value = (){ 49 import text.time.Convert : Convert; 50 51 with (Value.Builder()) 52 { 53 intValue = 23; 54 stringValue = "FOO"; 55 boolValue = true; 56 nestedValue = NestedValue("Bar"); 57 arrayValue = [1, 2, 3]; 58 assocArray = ["foo": NestedValue("bar"), "baz": NestedValue("whee")]; 59 nestedArray = [NestedValue("Foo"), NestedValue("Bar")]; 60 dateValue = Date(2000, 1, 2); 61 sysTimeValue = SysTime.fromISOExtString("2000-01-02T10:00:00Z"); 62 return value; 63 } 64 }(); 65 66 // when 67 const actual = testEncode(value); 68 69 const expected = ` 70 { 71 "IntValueElement": 23, 72 "StringValueElement": "FOO", 73 "BoolValueElement": true, 74 "NestedElement": { 75 "Element": "Bar" 76 }, 77 "ArrayElement": [1, 2, 3], 78 "AssocArrayElement": { 79 "baz": { "Element": "whee" }, 80 "foo": { "Element": "bar" } 81 }, 82 "NestedArray": [ 83 { "Element": "Foo" }, 84 { "Element": "Bar" } 85 ], 86 "DateElement": "2000-01-02", 87 "SysTimeElement": "2000-01-02T10:00:00Z" 88 } 89 `.parseJSON; 90 91 // then 92 actual.should.equal(expected); 93 } 94 95 @(prefix ~ "custom encoders are used on fields") 96 unittest 97 { 98 // given 99 const value = ValueWithEncoders("bla", "bla"); 100 101 // when 102 auto actual = testEncode(value); 103 104 // then 105 const expected = `{ "asFoo": "foo", "asBar": "bar" }`.parseJSON; 106 107 actual.should.equal(expected); 108 } 109 110 @(prefix ~ "custom encoders are used on a type") 111 unittest 112 { 113 // given 114 struct Value 115 { 116 TypeWithEncoder field; 117 118 mixin(GenerateAll); 119 } 120 121 const value = Value(TypeWithEncoder()); 122 123 // when 124 auto actual = testEncode(value); 125 126 // then 127 const expected = `{ "field": "123" }`.parseJSON; 128 129 actual.should.equal(expected); 130 } 131 132 @(prefix ~ "enums are encoded as strings") 133 unittest 134 { 135 enum Enum 136 { 137 A 138 } 139 140 struct Value 141 { 142 Enum field; 143 144 mixin(GenerateAll); 145 } 146 147 // given 148 const value = Value(Enum.A); 149 150 // when 151 const actual = testEncode(value); 152 153 // then 154 const expected = `{ "field": "A" }`.parseJSON; 155 156 actual.should.equal(expected); 157 } 158 159 @(prefix ~ "alias-this is encoded inline") 160 unittest 161 { 162 struct A 163 { 164 int value2; 165 166 mixin(GenerateAll); 167 } 168 169 struct B 170 { 171 int value1; 172 173 A a; 174 175 alias a this; 176 177 mixin(GenerateAll); 178 } 179 180 // given 181 const value = B(3, A(5)); 182 183 // when 184 const actual = testEncode(value); 185 186 // then 187 const expected = `{ "value1": 3, "value2": 5 }`.parseJSON; 188 189 actual.should.equal(expected); 190 } 191 192 @(prefix ~ "alias-this is encoded inline for aliased methods") 193 unittest 194 { 195 struct A 196 { 197 int value2; 198 199 mixin(GenerateAll); 200 } 201 202 struct B 203 { 204 int value1; 205 206 @ConstRead 207 A a_; 208 209 mixin(GenerateAll); 210 211 alias a this; 212 } 213 214 // given 215 const value = B(3, A(5)); 216 217 // when 218 const actual = testEncode(value); 219 220 // then 221 const expected = `{ "value1": 3, "value2": 5 }`.parseJSON; 222 223 actual.should.equal(expected); 224 } 225 226 @(prefix ~ "arrays of enums are encoded as strings") 227 unittest 228 { 229 enum Enum 230 { 231 A, 232 } 233 234 struct Value 235 { 236 Enum[] value; 237 238 mixin(GenerateAll); 239 } 240 241 // given 242 const value = Value([Enum.A]); 243 244 // when 245 auto actual = testEncode(value); 246 247 // then 248 const expected = `{ "value": ["A"] }`.parseJSON; 249 250 actual.should.equal(expected); 251 } 252 253 @(prefix ~ "transform functions may modify the values that are encoded") 254 unittest 255 { 256 import std.conv : to; 257 258 struct Inner 259 { 260 int value; 261 262 mixin(GenerateThis); 263 } 264 265 struct InnerDto 266 { 267 string encodedValue; 268 269 mixin(GenerateThis); 270 } 271 272 struct Struct 273 { 274 Inner inner; 275 276 mixin(GenerateThis); 277 } 278 279 InnerDto transform(Inner inner) 280 { 281 return InnerDto(inner.value.to!string); 282 } 283 284 // given 285 const value = Struct(Inner(5)); 286 287 // when 288 const actual = testEncode!(Struct, transform)(value); 289 290 // then 291 const expected = `{ "inner": { "encodedValue": "5" } }`.parseJSON; 292 293 actual.should.equal(expected); 294 } 295 296 @(prefix ~ "transform functions returning JSONValue") 297 unittest 298 { 299 import std.conv : to; 300 301 struct Inner 302 { 303 int value; 304 305 mixin(GenerateThis); 306 } 307 308 struct Struct 309 { 310 Inner inner; 311 312 mixin(GenerateThis); 313 } 314 315 JSONValue transform(Inner inner) 316 { 317 return JSONValue(inner.value.to!string); 318 } 319 320 // given 321 const value = Struct(Inner(5)); 322 323 // when 324 const actual = testEncode!(Struct, transform)(value); 325 326 // then 327 const expected = `{ "inner": "5" }`.parseJSON; 328 329 actual.should.equal(expected); 330 } 331 332 @(prefix ~ "struct with version_ field") 333 unittest 334 { 335 // given 336 struct Value 337 { 338 int version_; 339 340 mixin(GenerateAll); 341 } 342 343 const value = Value(1); 344 345 // when 346 auto actual = testEncode(value); 347 348 // then 349 const expected = `{ "version": 1 }`.parseJSON; 350 351 actual.should.equal(expected); 352 } 353 354 @(prefix ~ "encode class") 355 unittest 356 { 357 // given 358 class Value 359 { 360 int field; 361 362 mixin(GenerateAll); 363 } 364 365 const value = new Value(1); 366 367 // when 368 auto actual = testEncode(value); 369 370 // then 371 const expected = `{ "field": 1 }`.parseJSON; 372 373 actual.should.equal(expected); 374 } 375 376 @(prefix ~ "encode null object") 377 unittest 378 { 379 // given 380 class Value 381 { 382 mixin(GenerateAll); 383 } 384 385 // when 386 const actual = testEncode!Value(null); 387 388 // then 389 enum expected = `null`.parseJSON; 390 391 actual.should.equal(expected); 392 } 393 394 @(prefix ~ "encode null object with transform") 395 unittest 396 { 397 import std.conv : to; 398 399 JSONValue transform(const Object obj) 400 { 401 if (obj is null) 402 { 403 return JSONValue(null); 404 } 405 assert(false); 406 } 407 408 // given 409 const Object value = null; 410 411 // when 412 const actual = testEncode!(Object, transform)(value); 413 414 // then 415 enum expected = `null`.parseJSON; 416 417 actual.should.equal(expected); 418 } 419 420 static if (__traits(compiles, { import std.sumtype; })) 421 { 422 @(prefix ~ "encode std.sumtype") 423 unittest 424 { 425 import std.sumtype : SumType; 426 427 // given 428 alias S = SumType!(int, string); 429 430 const value = [S(1), S("foo")]; 431 432 // when 433 auto actual = testEncode(value); 434 435 // then 436 const expected = `[1, "foo"]`.parseJSON; 437 438 actual.should.equal(expected); 439 } 440 441 @(prefix ~ "encode std.sumtype with transform function") 442 unittest 443 { 444 import std.sumtype : match, SumType; 445 446 // given 447 alias S = SumType!(int, string); 448 449 string transform(const S s) 450 { 451 return s.match!((int i) => "int", (string s) => "string"); 452 } 453 454 const value = [S(1), S("foo")]; 455 456 // when 457 auto actual = testEncode!(S[], transform)(value); 458 459 // then 460 enum expected = `["int", "string"]`.parseJSON; 461 462 actual.should.equal(expected); 463 } 464 } 465 } 466 467 struct NestedValue 468 { 469 @(Json("Element")) 470 public string value; 471 472 mixin (GenerateAll); 473 } 474 475 struct Value 476 { 477 @(Json("IntValueElement")) 478 public int intValue; 479 480 @(Json("StringValueElement")) 481 public string stringValue; 482 483 @(Json("BoolValueElement")) 484 public bool boolValue; 485 486 @(Json("NestedElement")) 487 public NestedValue nestedValue; 488 489 @(Json("ArrayElement")) 490 public const int[] arrayValue; 491 492 @(Json("AssocArrayElement")) 493 public NestedValue[string] assocArray; 494 495 @(Json("NestedArray")) 496 public NestedValue[] nestedArray; 497 498 @(Json("DateElement")) 499 public Date dateValue; 500 501 @(Json("SysTimeElement")) 502 public SysTime sysTimeValue; 503 504 mixin (GenerateAll); 505 } 506 507 struct ValueWithEncoders 508 { 509 @(Json("asFoo")) 510 @(Json.Encode!asFoo) 511 public string foo; 512 513 @(Json("asBar")) 514 @(Json.Encode!asBar) 515 public string bar; 516 517 static JSONValue asFoo(string field) 518 { 519 field.should.equal("bla"); 520 521 return JSONValue("foo"); 522 } 523 524 static JSONValue asBar(string field) 525 { 526 field.should.equal("bla"); 527 528 return JSONValue("bar"); 529 } 530 531 mixin(GenerateThis); 532 } 533 534 @(Json.Encode!encodeTypeWithEncoder) 535 struct TypeWithEncoder 536 { 537 } 538 539 JSONValue encodeTypeWithEncoder(TypeWithEncoder) 540 { 541 return JSONValue("123"); 542 }