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 }