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 @("aggregate types are encoded to JSON text")
11 unittest
12 {
13     const expected = `
14     {
15         "IntValueElement": 23,
16         "StringValueElement": "FOO",
17         "BoolValueElement": true,
18         "NestedElement": {
19             "Element": "Bar"
20         },
21         "ArrayElement": [1, 2, 3],
22         "AssocArrayElement": {
23             "baz": { "Element": "whee" },
24             "foo": { "Element": "bar" }
25         },
26         "NestedArray": [
27             { "Element": "Foo" },
28             { "Element": "Bar" }
29         ],
30         "DateElement": "2000-01-02",
31         "SysTimeElement": "2000-01-02T10:00:00Z"
32     }
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             assocArray = ["foo": NestedValue("bar"), "baz": NestedValue("whee")];
47             nestedArray = [NestedValue("Foo"), NestedValue("Bar")];
48             dateValue = Date(2000, 1, 2);
49             sysTimeValue = SysTime.fromISOExtString("2000-01-02T10:00:00Z");
50             return value;
51         }
52     }();
53 
54     // when
55     const actualJson = encode(value).parseJSON;
56 
57     // then
58     const expectedJson = expected.parseJSON;
59 
60     actualJson.should.equal(expectedJson);
61 }
62 
63 @("custom encoders are used on fields")
64 unittest
65 {
66     // given
67     const value = ValueWithEncoders("bla", "bla");
68 
69     // when
70     auto text = encode(value);
71 
72     // then
73     const expected = `{ "asFoo": "foo", "asBar": "bar" }`;
74 
75     text.parseJSON.should.equal(expected.parseJSON);
76 }
77 
78 @("custom encoders are used on a type")
79 unittest
80 {
81     // given
82     struct Value
83     {
84         TypeWithEncoder field;
85 
86         mixin(GenerateAll);
87     }
88 
89     const value = Value(TypeWithEncoder());
90 
91     // when
92     auto text = encode(value);
93 
94     // then
95     const expected = `{ "field": "123" }`;
96 
97     text.parseJSON.should.equal(expected.parseJSON);
98 }
99 
100 @("enums are encoded as strings")
101 unittest
102 {
103     enum Enum
104     {
105         A
106     }
107 
108     struct Value
109     {
110         Enum field;
111 
112         mixin(GenerateAll);
113     }
114 
115     // given
116     const value = Value(Enum.A);
117 
118     // when
119     const text = encode(value);
120 
121     // then
122     const expected = `{ "field": "A" }`;
123 
124     text.parseJSON.should.equal(expected.parseJSON);
125 }
126 
127 @("alias-this is encoded inline")
128 unittest
129 {
130     struct A
131     {
132         int value2;
133 
134         mixin(GenerateAll);
135     }
136 
137     struct B
138     {
139         int value1;
140 
141         A a;
142 
143         alias a this;
144 
145         mixin(GenerateAll);
146     }
147 
148     // given
149     const value = B(3, A(5));
150 
151     // when
152     const actual = encode(value);
153 
154     // then
155     const expected = `{ "value1": 3, "value2": 5 }`;
156 
157     actual.parseJSON.should.equal(expected.parseJSON);
158 }
159 
160 @("alias-this is encoded inline for aliased methods")
161 unittest
162 {
163     struct A
164     {
165         int value2;
166 
167         mixin(GenerateAll);
168     }
169 
170     struct B
171     {
172         int value1;
173 
174         @ConstRead
175         A a_;
176 
177         mixin(GenerateAll);
178 
179         alias a this;
180     }
181 
182     // given
183     const value = B(3, A(5));
184 
185     // when
186     const actual = encode(value);
187 
188     // then
189     const expected = `{ "value1": 3, "value2": 5 }`;
190 
191     actual.parseJSON.should.equal(expected.parseJSON);
192 }
193 
194 @("arrays of enums are encoded as strings")
195 unittest
196 {
197     enum Enum
198     {
199         A,
200     }
201 
202     struct Value
203     {
204         Enum[] value;
205 
206         mixin(GenerateAll);
207     }
208 
209     // given
210     const value = Value([Enum.A]);
211 
212     // when
213     auto text = encode(value);
214 
215     // then
216     const expected = `{ "value": ["A"] }`;
217 
218     text.parseJSON.should.equal(expected.parseJSON);
219 }
220 
221 struct NestedValue
222 {
223     @(Json("Element"))
224     public string value;
225 
226     mixin (GenerateAll);
227 }
228 
229 struct Value
230 {
231     @(Json("IntValueElement"))
232     public int intValue;
233 
234     @(Json("StringValueElement"))
235     public string stringValue;
236 
237     @(Json("BoolValueElement"))
238     public bool boolValue;
239 
240     @(Json("NestedElement"))
241     public NestedValue nestedValue;
242 
243     @(Json("ArrayElement"))
244     public const int[] arrayValue;
245 
246     @(Json("AssocArrayElement"))
247     public NestedValue[string] assocArray;
248 
249     @(Json("NestedArray"))
250     public NestedValue[] nestedArray;
251 
252     @(Json("DateElement"))
253     public Date dateValue;
254 
255     @(Json("SysTimeElement"))
256     public SysTime sysTimeValue;
257 
258     mixin (GenerateAll);
259 }
260 
261 struct ValueWithEncoders
262 {
263     @(Json("asFoo"))
264     @(Json.Encode!asFoo)
265     public string foo;
266 
267     @(Json("asBar"))
268     @(Json.Encode!asBar)
269     public string bar;
270 
271     static JSONValue asFoo(string field)
272     {
273         field.should.equal("bla");
274 
275         return JSONValue("foo");
276     }
277 
278     static JSONValue asBar(string field)
279     {
280         field.should.equal("bla");
281 
282         return JSONValue("bar");
283     }
284 
285     mixin(GenerateThis);
286 }
287 
288 @(Json.Encode!encodeTypeWithEncoder)
289 struct TypeWithEncoder
290 {
291 }
292 
293 JSONValue encodeTypeWithEncoder(TypeWithEncoder)
294 {
295     return JSONValue("123");
296 }
297 
298 @("transform functions may modify the values that are encoded")
299 unittest
300 {
301     import std.conv : to;
302 
303     struct Inner
304     {
305         int value;
306 
307         mixin(GenerateThis);
308     }
309 
310     struct InnerDto
311     {
312         string encodedValue;
313 
314         mixin(GenerateThis);
315     }
316 
317     struct Struct
318     {
319         Inner inner;
320 
321         mixin(GenerateThis);
322     }
323 
324     InnerDto transform(Inner inner)
325     {
326         return InnerDto(inner.value.to!string);
327     }
328 
329     // given
330     const value = Struct(Inner(5));
331 
332     // when
333     const actual = encode!(Struct, transform)(value);
334 
335     // then
336     const expected = `{ "inner": { "encodedValue": "5" } }`;
337 
338     actual.parseJSON.should.equal(expected.parseJSON);
339 }
340 
341 @("transform functions returning JSONValue")
342 unittest
343 {
344     import std.conv : to;
345 
346     struct Inner
347     {
348         int value;
349 
350         mixin(GenerateThis);
351     }
352 
353     struct Struct
354     {
355         Inner inner;
356 
357         mixin(GenerateThis);
358     }
359 
360     JSONValue transform(Inner inner)
361     {
362         return JSONValue(inner.value.to!string);
363     }
364 
365     // given
366     const value = Struct(Inner(5));
367 
368     // when
369     const actual = encode!(Struct, transform)(value);
370 
371     // then
372     const expected = `{ "inner": "5" }`;
373 
374     actual.parseJSON.should.equal(expected.parseJSON);
375 }
376 
377 @("struct with version_ field")
378 unittest
379 {
380     // given
381     struct Value
382     {
383         int version_;
384 
385         mixin(GenerateAll);
386     }
387 
388     const value = Value(1);
389 
390     // when
391     auto text = encode(value);
392 
393     // then
394     const expected = `{ "version": 1 }`;
395 
396     text.parseJSON.should.equal(expected.parseJSON);
397 }