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 static if (__traits(compiles, { import std.sumtype; })) 377 { 378 @(prefix ~ "encode std.sumtype") 379 unittest 380 { 381 import std.sumtype : SumType; 382 383 // given 384 alias S = SumType!(int, string); 385 386 const value = [S(1), S("foo")]; 387 388 // when 389 auto actual = testEncode(value); 390 391 // then 392 const expected = `[1, "foo"]`.parseJSON; 393 394 actual.should.equal(expected); 395 } 396 } 397 } 398 399 struct NestedValue 400 { 401 @(Json("Element")) 402 public string value; 403 404 mixin (GenerateAll); 405 } 406 407 struct Value 408 { 409 @(Json("IntValueElement")) 410 public int intValue; 411 412 @(Json("StringValueElement")) 413 public string stringValue; 414 415 @(Json("BoolValueElement")) 416 public bool boolValue; 417 418 @(Json("NestedElement")) 419 public NestedValue nestedValue; 420 421 @(Json("ArrayElement")) 422 public const int[] arrayValue; 423 424 @(Json("AssocArrayElement")) 425 public NestedValue[string] assocArray; 426 427 @(Json("NestedArray")) 428 public NestedValue[] nestedArray; 429 430 @(Json("DateElement")) 431 public Date dateValue; 432 433 @(Json("SysTimeElement")) 434 public SysTime sysTimeValue; 435 436 mixin (GenerateAll); 437 } 438 439 struct ValueWithEncoders 440 { 441 @(Json("asFoo")) 442 @(Json.Encode!asFoo) 443 public string foo; 444 445 @(Json("asBar")) 446 @(Json.Encode!asBar) 447 public string bar; 448 449 static JSONValue asFoo(string field) 450 { 451 field.should.equal("bla"); 452 453 return JSONValue("foo"); 454 } 455 456 static JSONValue asBar(string field) 457 { 458 field.should.equal("bla"); 459 460 return JSONValue("bar"); 461 } 462 463 mixin(GenerateThis); 464 } 465 466 @(Json.Encode!encodeTypeWithEncoder) 467 struct TypeWithEncoder 468 { 469 } 470 471 JSONValue encodeTypeWithEncoder(TypeWithEncoder) 472 { 473 return JSONValue("123"); 474 }