1 module fluentasserts.vibe.json;
2 
3 version(Have_vibe_d_data):
4 
5 import std.exception, std.conv, std.traits;
6 import std.array, std.algorithm, std.typecons;
7 import std.uni, std.string;
8 
9 import vibe.data.json;
10 import fluentasserts.core.base;
11 import fluentasserts.core.results;
12 
13 import fluentasserts.core.serializers;
14 import fluentasserts.core.operations.equal;
15 import fluentasserts.core.operations.arrayEqual;
16 import fluentasserts.core.operations.contain;
17 import fluentasserts.core.operations.startWith;
18 import fluentasserts.core.operations.endWith;
19 import fluentasserts.core.operations.registry;
20 import fluentasserts.core.operations.lessThan;
21 import fluentasserts.core.operations.greaterThan;
22 import fluentasserts.core.operations.between;
23 import fluentasserts.core.operations.approximately;
24 
25 static this() {
26   SerializerRegistry.instance.register(&jsonToString);
27   Registry.instance.register!(Json, Json[])("equal", &fluentasserts.core.operations.equal.equal);
28   Registry.instance.register!(Json[], Json)("equal", &fluentasserts.core.operations.equal.equal);
29   Registry.instance.register!(Json[], Json[])("equal", &fluentasserts.core.operations.arrayEqual.arrayEqual);
30   Registry.instance.register!(Json[][], Json[][])("equal", &fluentasserts.core.operations.arrayEqual.arrayEqual);
31   Registry.instance.register!(Json, Json[][])("equal", &fluentasserts.core.operations.arrayEqual.arrayEqual);
32 
33   static foreach(Type; BasicNumericTypes) {
34     Registry.instance.register!(Json, Type[])("equal", &fluentasserts.core.operations.equal.equal);
35     Registry.instance.register!(Json, Type)("lessThan", &fluentasserts.core.operations.lessThan.lessThan!Type);
36     Registry.instance.register!(Json, Type)("greaterThan", &fluentasserts.core.operations.greaterThan.greaterThan!Type);
37     Registry.instance.register!(Json, Type)("between", &fluentasserts.core.operations.between.between!Type);
38     Registry.instance.register!(Json, Type)("approximately", &fluentasserts.core.operations.approximately.approximately);
39   }
40 
41   static foreach(Type; StringTypes) {
42     Registry.instance.register(extractTypes!(Json[])[0], "void[]", "equal", &arrayEqual);
43 
44     Registry.instance.register!(Type[], Json[])("equal", &arrayEqual);
45 
46     Registry.instance.register!(Type, Json[])("contain", &contain);
47     Registry.instance.register!(Type, Json)("contain", &contain);
48     Registry.instance.register!(Type[], Json[])("contain", &arrayContain);
49     Registry.instance.register!(Type[], Json[])("containOnly", &arrayContainOnly);
50 
51     Registry.instance.register!(Type, Json)("startWith", &startWith);
52     Registry.instance.register!(Type, Json)("endWith", &endWith);
53   }
54 }
55 
56 string jsonToString(Json value) {
57   return jsonToString(value, 0);
58 }
59 
60 string jsonToString(Json value, size_t level) {
61 
62   if(value.type == Json.Type.array) {
63     string prefix = rightJustifier(``, level * 2, ' ').array;
64     return `[` ~ value.byValue.map!(a => jsonToString(a, level)).join(", ") ~ `]`;
65   }
66 
67   if(value.type == Json.Type.object) {
68     auto keys = value.keys.sort;
69 
70     if(keys.length == 0) {
71       return `{}`;
72     }
73 
74     string prefix = rightJustifier(``, 2 + level * 2, ' ').array;
75     string endPrefix = rightJustifier(``, level * 2, ' ').array;
76 
77     return "{\n" ~ keys.map!(key => prefix ~ `"` ~ key ~ `": ` ~ value[key].jsonToString(level + 1)).join(",\n") ~ "\n" ~ endPrefix ~ "}";
78   }
79 
80   if(value.type == Json.Type.null_) {
81     return "null";
82   }
83 
84   if(value.type == Json.Type.undefined) {
85     return "undefined";
86   }
87 
88   if(value.type == Json.Type..string) {
89     return `"` ~ value.to!string ~ `"`;
90   }
91 
92   return value.to!string;
93 }
94 
95 /// Get all the keys from your Json object
96 string[] keys(Json obj, const string file = __FILE__, const size_t line = __LINE__) @trusted {
97   string[] list;
98 
99   if(obj.type != Json.Type.object) {
100     IResult[] results = [ cast(IResult) new MessageResult("Invalid Json type."),
101                           cast(IResult) new ExpectedActualResult("object", obj.type.to!string),
102                           cast(IResult) new SourceResult(file, line) ];
103 
104     throw new TestException(results, file, line);
105   }
106 
107   static if(typeof(obj.byKeyValue).stringof == "Rng") {
108     foreach(string key, Json value; obj.byKeyValue) {
109       list ~= key;
110     }
111 
112     list = list.sort.array;
113 
114     return list;
115   } else {
116     pragma(msg, "Json.keys is not compatible with your vibe.d version");
117     assert(false, "Json.keys is not compatible with your vibe.d version");
118   }
119 }
120 
121 /// Empty Json object keys
122 unittest {
123   Json.emptyObject.keys.length.should.equal(0);
124 }
125 
126 /// Json object keys
127 unittest {
128   auto obj = Json.emptyObject;
129   obj["key1"] = 1;
130   obj["key2"] = 3;
131 
132   obj.keys.should.containOnly(["key1", "key2"]);
133 }
134 
135 /// Json array keys
136 unittest {
137   auto obj = Json.emptyArray;
138 
139   ({
140     obj.keys.should.contain(["key1", "key2"]);
141   }).should.throwAnyException.msg.should.startWith("Invalid Json type.");
142 }
143 
144 /// Get all the keys from your Json object. The levels will be separated by `.` or `[]`
145 string[] nestedKeys(Json obj) @trusted {
146   return obj.flatten.byKeyValue.map!"a.key".array;
147 }
148 
149 /// Empty Json object keys
150 unittest {
151   Json.emptyObject.nestedKeys.length.should.equal(0);
152 }
153 
154 /// Get all keys from nested object
155 unittest {
156   auto obj = Json.emptyObject;
157   obj["key1"] = 1;
158   obj["key2"] = 2;
159   obj["key3"] = Json.emptyObject;
160   obj["key3"]["item1"] = "3";
161   obj["key3"]["item2"] = Json.emptyObject;
162   obj["key3"]["item2"]["item4"] = Json.emptyObject;
163   obj["key3"]["item2"]["item5"] = Json.emptyObject;
164   obj["key3"]["item2"]["item5"]["item6"] = Json.emptyObject;
165 
166   obj.nestedKeys.should.containOnly(["key1", "key2", "key3.item1", "key3.item2.item4", "key3.item2.item5.item6"]);
167 }
168 
169 
170 /// Get all keys from nested objects inside an array
171 unittest {
172   auto obj = Json.emptyObject;
173   Json elm = Json.emptyObject;
174   elm["item5"] = Json.emptyObject;
175   elm["item5"]["item6"] = Json.emptyObject;
176 
177   obj["key2"] = Json.emptyArray;
178   obj["key3"] = Json.emptyArray;
179   obj["key3"] ~= Json("3");
180   obj["key3"] ~= Json.emptyObject;
181   obj["key3"] ~= elm;
182   obj["key3"] ~= [ Json.emptyArray ];
183 
184   obj.nestedKeys.should.containOnly(["key2", "key3[0]", "key3[1]", "key3[2].item5.item6", "key3[3]"]);
185 }
186 
187 /// Takes a nested Json object and moves the values to a Json assoc array where the key
188 /// is the path from the original object to that value
189 Json[string] flatten(Json object) @trusted {
190   Json[string] elements;
191 
192   auto root = tuple("", object);
193   Tuple!(string, Json)[] queue = [ root ];
194 
195   while(queue.length > 0) {
196     auto element = queue[0];
197 
198     if(element[0] != "") {
199       if(element[1].type != Json.Type.object && element[1].type != Json.Type.array) {
200         elements[element[0]] = element[1];
201       }
202 
203       if(element[1].type == Json.Type.object && element[1].length == 0) {
204         elements[element[0]] = element[1];
205       }
206 
207       if(element[1].type == Json.Type.array && element[1].length == 0) {
208         elements[element[0]] = element[1];
209       }
210     }
211 
212     if(element[1].type == Json.Type.object) {
213       foreach(string key, value; element[1].byKeyValue) {
214         string nextKey = key;
215 
216         if(element[0] != "") {
217           nextKey = element[0] ~ "." ~ nextKey;
218         }
219 
220         queue ~= tuple(nextKey, value);
221       }
222     }
223 
224     if(element[1].type == Json.Type.array) {
225       size_t index;
226 
227       foreach(value; element[1].byValue) {
228         string nextKey = element[0] ~ "[" ~ index.to!string ~ "]";
229 
230         queue ~= tuple(nextKey, value);
231         index++;
232       }
233     }
234 
235     queue = queue[1..$];
236   }
237 
238   return elements;
239 }
240 
241 /// Get a flatten object
242 unittest {
243   auto obj = Json.emptyObject;
244   obj["key1"] = 1;
245   obj["key2"] = 2;
246   obj["key3"] = Json.emptyObject;
247   obj["key3"]["item1"] = "3";
248   obj["key3"]["item2"] = Json.emptyObject;
249   obj["key3"]["item2"]["item4"] = Json.emptyObject;
250   obj["key3"]["item2"]["item5"] = Json.emptyObject;
251   obj["key3"]["item2"]["item5"]["item6"] = Json.emptyObject;
252 
253   auto result = obj.flatten;
254   result.byKeyValue.map!(a => a.key).should.containOnly(["key1", "key2", "key3.item1", "key3.item2.item4", "key3.item2.item5.item6"]);
255   result["key1"].should.equal(1);
256   result["key2"].should.equal(2);
257   result["key3.item1"].should.equal("3");
258   result["key3.item2.item4"].should.equal(Json.emptyObject);
259   result["key3.item2.item5.item6"].should.equal(Json.emptyObject);
260 }
261 
262 auto unpackJsonArray(T : U[], U)(Json data) if(!isArray!U && isBasicType!U) {
263   return data.byValue.map!(a => a.to!U).array.dup;
264 }
265 
266 auto unpackJsonArray(T : U[], U)(Json data) if(!isArray!U && is(Unqual!U == Json)) {
267   U[] result;
268 
269   foreach(element; data.byValue) {
270     result ~= element;
271   }
272 
273   return result;
274 }
275 
276 auto unpackJsonArray(T : U[], U)(Json data) if(isArray!(U) && !isSomeString!(U[])) {
277   U[] result;
278 
279   foreach(element; data.byValue) {
280     result ~= unpackJsonArray!(U)(element);
281   }
282 
283   return result;
284 }
285 
286 /*
287 struct ShouldJson(T) {
288   private const T testData;
289 
290   this(U)(U value) {
291     valueEvaluation = value.evaluation;
292     testData = value.value;
293   }
294 
295   mixin ShouldCommons;
296   mixin ShouldThrowableCommons;
297 
298   auto validateJsonType(const T someValue, const string file, const size_t line) {
299     auto haveSameType = someValue.type == testData.type;
300 
301     string expected = "a Json.Type." ~ testData.type.to!string;
302     string actual = "a Json.Type." ~ someValue.type.to!string;
303 
304     Message[] msg;
305 
306     if(!haveSameType) {
307       msg = [
308         Message(false, "They have incompatible types "),
309         Message(false, "`Json.Type."),
310         Message(true, testData.type.to!string),
311         Message(false, "` != `Json.Type."),
312         Message(true, someValue.type.to!string),
313         Message(false, "`.")
314       ];
315 
316       return result(haveSameType, msg, [ new ExpectedActualResult(actual, expected) ], file, line);
317     }
318 
319     return result(haveSameType, msg, [ ], file, line);
320   }
321 
322   auto validateJsonType(U)(const string file, const size_t line) {
323     Json.Type someType = Json.Type.undefined;
324     string expected;
325     string actual = "a Json.Type." ~ testData.type.to!string;
326     bool haveSameType;
327 
328     static if(is(U == string)) {
329       someType = Json.Type.string;
330       expected = "a string or Json.Type." ~ someType.to!string;
331       haveSameType = someType == testData.type;
332     }
333 
334     static if(is(U == byte) || is(U == short) || is(U == int) || is(U == long) ||
335               is(U == ubyte) || is(U == ushort) || is(U == uint) || is(U == ulong)) {
336       someType = Json.Type.int_;
337       haveSameType = Json.Type.int_ == testData.type || Json.Type.float_ == testData.type;
338 
339       expected = "a " ~ U.stringof ~ " or Json.Type." ~ someType.to!string;
340     }
341 
342     static if(is(U == float) || is(U == double)) {
343       someType = Json.Type.float_;
344       haveSameType = Json.Type.int_ == testData.type || Json.Type.float_ == testData.type;
345 
346       expected = "a " ~ U.stringof ~ " or Json.Type." ~ someType.to!string;
347     }
348 
349     static if(is(U == bool)) {
350       someType = Json.Type.bool_;
351       haveSameType = someType == testData.type;
352 
353       expected = "a " ~ U.stringof ~ " or Json.Type." ~ someType.to!string;
354     }
355 
356     static if(isArray!U && !isSomeString!U) {
357       someType = Json.Type.array;
358       haveSameType = someType == testData.type;
359 
360       expected = "a " ~ U.stringof ~ "[] or Json.Type." ~ someType.to!string;
361     }
362 
363     if(expected == "") {
364       expected = "a Json.Type." ~ someType.to!string;
365     }
366 
367     Message[] msg = [
368       Message(false, "They have incompatible types "),
369       Message(false, "`Json.Type."),
370       Message(true, testData.type.to!string),
371       Message(false, "` != `"),
372       Message(true, U.stringof),
373       Message(false, "`.")
374     ];
375 
376     return result(haveSameType, msg, [ new ExpectedActualResult(expected, actual) ], file, line);
377   }
378 
379   /// Check if it equals with a value
380   auto equal(U)(const U someValue, const string file = __FILE__, const size_t line = __LINE__) if(!isArray!U || isSomeString!U) {
381     addMessage(" equal `");
382     addValue(someValue.to!string);
383     addMessage("`");
384 
385     static if(is(U == string) || std.traits.isNumeric!U || is(U == bool)) {
386       U nativeVal;
387 
388       try {
389         nativeVal = testData.to!U;
390       } catch(ConvException e) {
391         addMessage(". ");
392         if(e.msg.length > 0 && e.msg[0].toUpper == e.msg[0]) {
393           addValue(e.msg);
394         } else {
395           addMessage("During conversion ");
396           auto msg = e.msg;
397 
398           if(msg.endsWith(".")) {
399             msg = msg[0..$-1];
400           }
401 
402           addValue(msg);
403         }
404       }
405       validateException;
406 
407       if(expectedValue) {
408         auto typeResult = validateJsonType!U(file, line);
409 
410         if(typeResult.willThrow) {
411           return typeResult;
412         }
413 
414         return expect(nativeVal, file, line).to.equal(someValue);
415       } else {
416         return expect(nativeVal, file, line).to.not.equal(someValue);
417       }
418     } else static if(is(U == Json)) {
419       return equalJson(someValue, file, line);
420     } else {
421       static assert(false, "You can not validate `Json` against `" ~ U.stringof ~ "`");
422     }
423   }
424 
425   /// Check if it equals to an array
426   auto equal(U)(const U[] someArray, const string file = __FILE__, const size_t line = __LINE__) {
427     addMessage(" equal `");
428     addValue(someArray.to!string);
429     addMessage("`");
430 
431     validateException;
432 
433     Unqual!U[] nativeVal;
434 
435     try {
436       nativeVal = unpackJsonArray!(U[])(testData);
437     } catch(Exception e) {
438       addMessage(". ");
439       if(e.msg.length > 0 && e.msg[0].toUpper == e.msg[0]) {
440         addValue(e.msg);
441       } else {
442         addMessage("During conversion ");
443         addValue(e.msg);
444       }
445     }
446 
447     static if(is(U == string) || std.traits.isNumeric!U || is(U == bool)) {
448 
449       if(expectedValue) {
450         auto typeResult = validateJsonType!(U[])(file, line);
451 
452         if(typeResult.willThrow) {
453           return typeResult;
454         }
455 
456         return nativeVal.should.equal(someArray, file, line);
457       } else {
458         return nativeVal.should.not.equal(someArray, file, line);
459       }
460     } else static if(isArray!U) {
461       if(expectedValue) {
462         auto typeResult = validateJsonType!(U[])(file, line);
463 
464         if(typeResult.willThrow) {
465           return typeResult;
466         }
467 
468         return nativeVal.should.equal(someArray, file, line);
469       } else {
470         return nativeVal.should.not.equal(someArray, file, line);
471       }
472     } else static if(is(U == Json)) {
473 
474       if(expectedValue) {
475         auto typeResult = validateJsonType!(U[])(file, line);
476 
477         if(typeResult.willThrow) {
478           return typeResult;
479         }
480 
481         return nativeVal.should.equal(someArray, file, line);
482       } else {
483         return nativeVal.should.not.equal(someArray, file, line);
484       }
485     } else {
486       static assert(false, "You can not validate `Json` against `" ~ U.stringof ~ "`");
487     }
488   }
489 
490   auto greaterThan(U)(const U someValue, const string file = __FILE__, const size_t line = __LINE__) if(isNumeric!U) {
491     auto enforceResult = enforceNumericJson("greaterThan", file, line);
492 
493     if(enforceResult.willThrow) {
494       return enforceResult;
495     }
496 
497     if(expectedValue) {
498       return testData.to!U.should.be.greaterThan(someValue, file, line);
499     } else {
500       return testData.to!U.should.not.be.greaterThan(someValue, file, line);
501     }
502   }
503 
504   auto lessThan(U)(const U someValue, const string file = __FILE__, const size_t line = __LINE__) if(isNumeric!U) {
505     auto enforceResult = enforceNumericJson("lessThan", file, line);
506 
507     if(enforceResult.willThrow) {
508       return enforceResult;
509     }
510 
511     if(expectedValue) {
512       return testData.to!U.should.be.lessThan(someValue, file, line);
513     } else {
514       return testData.to!U.should.not.be.lessThan(someValue, file, line);
515     }
516   }
517 
518   auto between(U)(const U limit1, const U limit2, const string file = __FILE__, const size_t line = __LINE__) if(isNumeric!U) {
519     auto enforceResult = enforceNumericJson("between", file, line);
520 
521     if(enforceResult.willThrow) {
522       return enforceResult;
523     }
524 
525     if(expectedValue) {
526       return testData.to!U.should.be.between(limit1, limit2, file, line);
527     } else {
528       return testData.to!U.should.not.be.between(limit1, limit2, file, line);
529     }
530   }
531 
532   auto approximately(U)(const U someValue, const U delta, const string file = __FILE__, const size_t line = __LINE__) if(isNumeric!U) {
533     auto enforceResult = enforceNumericJson("approximately", file, line);
534 
535     if(enforceResult.willThrow) {
536       return enforceResult;
537     }
538 
539     if(expectedValue) {
540       return testData.to!U.should.be.approximately(someValue, delta, file, line);
541     } else {
542       return testData.to!U.should.not.be.approximately(someValue, delta, file, line);
543     }
544   }
545 
546   private {
547     auto enforceNumericJson(string functionName, const string file, const size_t line) {
548       if(!expectedValue) {
549         return Result.success;
550       }
551 
552       auto typeResult = expect(
553         testData.type == Json.Type.int_ ||
554         testData.type == Json.Type.float_,
555         file, line
556       ).to.equal(true);
557 
558       typeResult.message = new MessageResult(" must contain a number to use " ~ functionName ~ ". A `");
559       typeResult.message.addValue("Json.Type." ~ testData.type.to!string);
560       typeResult.message.addText("` was found instead.");
561 
562       typeResult.results = [ new ExpectedActualResult("Json.Type.int_ or Json.Type.float_", "Json.Type." ~ testData.type.to!string) ];
563 
564       return typeResult;
565     }
566 
567     auto equalJson(const Json someValue, const string file, const size_t line) {
568       if(expectedValue) {
569         auto typeResult = validateJsonType(someValue, file, line);
570 
571         if(typeResult.willThrow) {
572           return typeResult;
573         }
574       }
575 
576       beginCheck;
577       if(testData.type == Json.Type.string) {
578         return equal(someValue.to!string, file, line);
579       }
580 
581       if(testData.type == Json.Type.int_) {
582         return equal(someValue.to!long, file, line);
583       }
584 
585       if(testData.type == Json.Type.float_) {
586         return equal(someValue.to!double, file, line);
587       }
588 
589       if(testData.type == Json.Type.bool_) {
590         return equal(someValue.to!bool, file, line);
591       }
592 
593       if(testData.type == Json.Type.array) {
594         Json[] values;
595 
596         foreach(value; someValue.byValue) {
597           values ~= value;
598         }
599 
600         return equal(values, file, line);
601       }
602 
603       auto isSame = testData == someValue;
604       auto expected = expectedValue ? someValue.toPrettyString : "something different than the Actual data";
605 
606       IResult[] results;
607       auto message = new MessageResult("");
608       message.addText("\n");
609       message.addValue("Expected:");
610       message.addText("\n" ~ expected ~ "\n\n");
611       message.addValue("Actual:");
612       message.addText("\n" ~ testData.toPrettyString);
613 
614       results ~= message;
615 
616       if(expectedValue) {
617         auto flattenTestData = testData.flatten;
618         auto flattenSomeValue = someValue.flatten;
619 
620         foreach(string key, value; flattenTestData) {
621           if(key in flattenSomeValue && flattenSomeValue[key] != value) {
622             results ~= new ExpectedActualResult(key,
623               flattenSomeValue[key].to!string ~ " (Json.Type." ~ flattenSomeValue[key].type.to!string ~ ")",
624               value.to!string ~ " (Json.Type." ~ value.type.to!string ~ ")");
625           }
626         }
627 
628         auto infoResult = new ListInfoResult();
629         auto comparison = ListComparison!string(someValue.nestedKeys, testData.nestedKeys);
630 
631         infoResult.add("Extra key", "Extra keys", comparison.extra);
632         infoResult.add("Missing key", "Missing keys", comparison.missing);
633 
634         results ~= infoResult;
635       }
636 
637       return result(isSame, [], results, file, line);
638     }
639   }
640 }*/
641 
642 version(unittest) {
643   import std.string;
644 }
645 
646 /// It should be able to compare 2 empty json objects
647 unittest {
648   Json.emptyObject.should.equal(Json.emptyObject);
649 }
650 
651 /// It should be able to compare an empty object with an empty array
652 unittest {
653   auto msg = ({
654     Json.emptyObject.should.equal(Json.emptyArray);
655   }).should.throwException!TestException.msg;
656 
657   msg.split("\n")[0].strip.should.equal(`Json.emptyObject should equal []. {} is not equal to [].`);
658   msg.split("\n")[2].strip.should.equal(`[-[]][+{}]`);
659   msg.split("\n")[4].strip.should.equal("Expected:[]");
660   msg.split("\n")[5].strip.should.equal("Actual:{}");
661 
662   ({
663     Json.emptyObject.should.not.equal(Json.emptyArray);
664   }).should.not.throwException!TestException;
665 }
666 
667 /// It should be able to compare two strings
668 unittest {
669   ({
670     Json("test string").should.equal("test string");
671     Json("other string").should.not.equal("test");
672   }).should.not.throwAnyException;
673 
674   ({
675     Json("test string").should.equal(Json("test string"));
676     Json("other string").should.not.equal(Json("test"));
677   }).should.not.throwAnyException;
678 
679   auto msg = ({
680     Json("test string").should.equal("test");
681   }).should.throwException!TestException.msg;
682 
683   msg.split("\n")[0].should.equal(`Json("test string") should equal "test". "test string" is not equal to "test".`);
684 }
685 
686 /// It throw on comparing a Json number with a string
687 unittest {
688   auto msg = ({
689     Json(4).should.equal("some string");
690   }).should.throwException!TestException.msg;
691 
692   msg.split("\n")[0].strip.should.equal(`Json(4) should equal "some string". 4 is not equal to "some string".`);
693   msg.split("\n")[2].strip.should.equal(`[-"some string"][+4]`);
694   msg.split("\n")[4].strip.should.equal(`Expected:"some string"`);
695   msg.split("\n")[5].strip.should.equal(`Actual:4`);
696 }
697 
698 /// It throws when you compare a Json string with integer values
699 unittest {
700   auto msg = ({
701     byte val = 4;
702     Json("some string").should.equal(val);
703   }).should.throwException!TestException.msg;
704 
705   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal 4. "some string" is not equal to 4.`);
706   msg.split("\n")[2].strip.should.equal(`[-4][+"some string"]`);
707   msg.split("\n")[4].strip.should.equal("Expected:4");
708   msg.split("\n")[5].strip.should.equal(`Actual:"some string"`);
709 
710   msg = ({
711     short val = 4;
712     Json("some string").should.equal(val);
713   }).should.throwException!TestException.msg;
714 
715   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal 4. "some string" is not equal to 4.`);
716 
717   msg = ({
718     int val = 4;
719     Json("some string").should.equal(val);
720   }).should.throwException!TestException.msg;
721 
722   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal 4. "some string" is not equal to 4.`);
723 
724   msg = ({
725     long val = 4;
726     Json("some string").should.equal(val);
727   }).should.throwException!TestException.msg;
728 
729   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal 4. "some string" is not equal to 4.`);
730 }
731 
732 /// It throws when you compare a Json string with unsigned integer values
733 unittest {
734   auto msg = ({
735     ubyte val = 4;
736     Json("some string").should.equal(val);
737   }).should.throwException!TestException.msg;
738 
739   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal 4. "some string" is not equal to 4.`);
740 
741   msg = ({
742     ushort val = 4;
743     Json("some string").should.equal(val);
744   }).should.throwException!TestException.msg;
745 
746   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal 4. "some string" is not equal to 4.`);
747 
748   msg = ({
749     uint val = 4;
750     Json("some string").should.equal(val);
751   }).should.throwException!TestException.msg;
752 
753   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal 4. "some string" is not equal to 4.`);
754 
755   msg = ({
756     ulong val = 4;
757     Json("some string").should.equal(val);
758   }).should.throwException!TestException.msg;
759 
760   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal 4. "some string" is not equal to 4.`);
761 }
762 
763 /// It throws when you compare a Json string with floating point values
764 unittest {
765   auto msg = ({
766     float val = 3.14;
767     Json("some string").should.equal(val);
768   }).should.throwException!TestException.msg;
769 
770   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal 3.14. "some string" is not equal to 3.14.`);
771   msg.split("\n")[2].strip.should.equal(`[-3.14][+"some string"]`);
772   msg.split("\n")[4].strip.should.equal("Expected:3.14");
773   msg.split("\n")[5].strip.should.equal(`Actual:"some string"`);
774 
775   msg = ({
776     double val = 3.14;
777     Json("some string").should.equal(val);
778   }).should.throwException!TestException.msg;
779 
780   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal 3.14. "some string" is not equal to 3.14.`);
781 }
782 
783 /// It throws when you compare a Json string with bool values
784 unittest {
785   auto msg = ({
786     Json("some string").should.equal(false);
787   }).should.throwException!TestException.msg;
788 
789   msg.split("\n")[0].strip.should.equal(`Json("some string") should equal false. "some string" is not equal to false.`);
790 }
791 
792 /// It should be able to compare two integers
793 unittest {
794   Json(4L).should.equal(4f);
795   Json(4).should.equal(4);
796   Json(4).should.not.equal(5);
797 
798   Json(4).should.equal(Json(4));
799   Json(4).should.not.equal(Json(5));
800   Json(4L).should.not.equal(Json(5f));
801 
802   auto msg = ({
803     Json(4).should.equal(5);
804   }).should.throwException!TestException.msg;
805 
806   msg.split("\n")[0].should.equal(`Json(4) should equal 5. 4 is not equal to 5.`);
807 
808   msg = ({
809     Json(4).should.equal(Json(5));
810   }).should.throwException!TestException.msg;
811 
812   msg.split("\n")[0].should.equal(`Json(4) should equal 5. 4 is not equal to 5.`);
813 }
814 
815 /// It throws on comparing an integer Json with a string
816 unittest {
817   auto msg = ({
818     Json(4).should.equal("5");
819   }).should.throwException!TestException.msg;
820 
821   msg.split("\n")[0].should.equal(`Json(4) should equal "5". 4 is not equal to "5".`);
822 
823   msg = ({
824     Json(4).should.equal(Json("5"));
825   }).should.throwException!TestException.msg;
826 
827   msg.split("\n")[0].should.equal(`Json(4) should equal "5". 4 is not equal to "5".`);
828 }
829 
830 /// It should be able to compare two floating point numbers
831 unittest {
832   Json(4f).should.equal(4L);
833   Json(4.3).should.equal(4.3);
834   Json(4.3).should.not.equal(5.3);
835 
836   Json(4.3).should.equal(Json(4.3));
837   Json(4.3).should.not.equal(Json(5.3));
838 
839   auto msg = ({
840     Json(4.3).should.equal(5.3);
841   }).should.throwException!TestException.msg;
842 
843   msg.split("\n")[0].should.equal("Json(4.3) should equal 5.3. 4.3 is not equal to 5.3.");
844 
845   msg = ({
846     Json(4.3).should.equal(Json(5.3));
847   }).should.throwException!TestException.msg;
848 
849   msg.split("\n")[0].should.equal("Json(4.3) should equal 5.3. 4.3 is not equal to 5.3.");
850 }
851 
852 /// It throws on comparing an floating point Json with a string
853 unittest {
854   auto msg = ({
855     Json(4f).should.equal("5");
856   }).should.throwException!TestException.msg;
857 
858   msg.split("\n")[0].should.equal(`Json(4f) should equal "5". 4 is not equal to "5".`);
859 
860   msg = ({
861     Json(4f).should.equal(Json("5"));
862   }).should.throwException!TestException.msg;
863 
864   msg.split("\n")[0].should.equal(`Json(4f) should equal "5". 4 is not equal to "5".`);
865 }
866 
867 /// It should be able to compare two booleans
868 unittest {
869   Json(true).should.equal(true);
870   Json(true).should.not.equal(false);
871 
872   Json(true).should.equal(Json(true));
873   Json(true).should.not.equal(Json(false));
874 
875   auto msg = ({
876     Json(true).should.equal(false);
877   }).should.throwException!TestException.msg;
878 
879   msg.split("\n")[0].should.equal("Json(true) should equal false. true is not equal to false.");
880 
881   msg = ({
882     Json(true).should.equal(Json(false));
883   }).should.throwException!TestException.msg;
884 
885   msg.split("\n")[0].should.equal("Json(true) should equal false. true is not equal to false.");
886 }
887 
888 /// It throws on comparing a bool Json with a string
889 unittest {
890   auto msg = ({
891     Json(true).should.equal("5");
892   }).should.throwException!TestException.msg;
893 
894   msg.split("\n")[0].should.equal(`Json(true) should equal "5". true is not equal to "5".`);
895 
896   msg = ({
897     Json(true).should.equal(Json("5"));
898   }).should.throwException!TestException.msg;
899 
900   msg.split("\n")[0].should.equal(`Json(true) should equal "5". true is not equal to "5".`);
901   msg.split("\n")[2].should.equal(`[-"5"][+true]`);
902   msg.split("\n")[4].should.equal(` Expected:"5"`);
903   msg.split("\n")[5].should.equal(`   Actual:true`);
904 }
905 
906 /// It should be able to compare two arrays
907 unittest {
908   Json[] elements = [Json(1), Json(2)];
909   Json[] otherElements = [Json(1), Json(2), Json(3)];
910 
911   Json(elements).should.equal([1, 2]);
912   Json(elements).should.not.equal([1, 2, 3]);
913 
914   Json(elements).should.equal(Json(elements));
915   Json(elements).should.not.equal(Json(otherElements));
916 
917   auto msg = ({
918     Json(elements).should.equal(otherElements);
919   }).should.throwException!TestException.msg;
920 
921   msg.split("\n")[0].should.equal("Json(elements) should equal [1, 2, 3]. [1, 2] is not equal to [1, 2, 3].");
922 
923   msg = ({
924     Json(elements).should.equal(otherElements);
925   }).should.throwException!TestException.msg;
926 
927   msg.split("\n")[0].should.equal("Json(elements) should equal [1, 2, 3]. [1, 2] is not equal to [1, 2, 3].");
928 }
929 
930 /// It throws on comparing a Json array with a string
931 unittest {
932   Json[] elements = [Json(1), Json(2)];
933   auto msg = ({
934     Json(elements).should.equal("5");
935   }).should.throwException!TestException.msg;
936 
937   msg.split("\n")[0].should.equal(`Json(elements) should equal "5". [1, 2] is not equal to "5".`);
938 
939   msg = ({
940     Json(elements).should.equal(Json("5"));
941   }).should.throwException!TestException.msg;
942 
943   msg.split("\n")[0].should.equal(`Json(elements) should equal "5". [1, 2] is not equal to "5".`);
944 }
945 
946 /// It should be able to compare two nested arrays
947 unittest {
948   Json[] element1 = [Json(1), Json(2)];
949   Json[] element2 = [Json(10), Json(20)];
950 
951   Json[] elements = [Json(element1), Json(element2)];
952   Json[] otherElements = [Json(element1), Json(element2), Json(element1)];
953 
954   Json(elements).should.equal([element1, element2]);
955   Json(elements).should.not.equal([element1, element2, element1]);
956 
957   Json(elements).should.equal(Json(elements));
958   Json(elements).should.not.equal(Json(otherElements));
959 
960   auto msg = ({
961     Json(elements).should.equal(otherElements);
962   }).should.throwException!TestException.msg;
963 
964   msg.split("\n")[0].should.equal("Json(elements) should equal [[1, 2], [10, 20], [1, 2]]. [[1, 2], [10, 20]] is not equal to [[1, 2], [10, 20], [1, 2]].");
965 
966   msg = ({
967     Json(elements).should.equal(Json(otherElements));
968   }).should.throwException!TestException.msg;
969 
970   msg.split("\n")[0].should.equal("Json(elements) should equal [[1, 2], [10, 20], [1, 2]]. [[1, 2], [10, 20]] is not equal to [[1, 2], [10, 20], [1, 2]].");
971 }
972 
973 /// It should be able to compare two nested arrays with different levels
974 unittest {
975   Json nestedElement = Json([Json(1), Json(2)]);
976 
977   Json[] elements = [nestedElement, Json(1)];
978   Json[] otherElements = [nestedElement, Json(1), nestedElement];
979 
980   Json(elements).should.equal([nestedElement, Json(1)]);
981   Json(elements).should.not.equal([nestedElement, Json(1), nestedElement]);
982 
983   Json(elements).should.equal(Json(elements));
984   Json(elements).should.not.equal(Json(otherElements));
985 
986   auto msg = ({
987     Json(elements).should.equal(otherElements);
988   }).should.throwException!TestException.msg;
989 
990   msg.split("\n")[0].should.equal("Json(elements) should equal [[1, 2], 1, [1, 2]]. [[1, 2], 1] is not equal to [[1, 2], 1, [1, 2]].");
991 
992   msg = ({
993     Json(elements).should.equal(Json(otherElements));
994   }).should.throwException!TestException.msg;
995 
996   msg.split("\n")[0].should.equal("Json(elements) should equal [[1, 2], 1, [1, 2]]. [[1, 2], 1] is not equal to [[1, 2], 1, [1, 2]].");
997 }
998 
999 /// It should find the key differences inside a Json object
1000 unittest {
1001   Json expectedObject = Json.emptyObject;
1002   Json testObject = Json.emptyObject;
1003   testObject["key"] = "some value";
1004   testObject["nested"] = Json.emptyObject;
1005   testObject["nested"]["item1"] = "hello";
1006   testObject["nested"]["item2"] = Json.emptyObject;
1007   testObject["nested"]["item2"]["value"] = "world";
1008 
1009   expectedObject["other"] = "other value";
1010 
1011   auto msg = ({
1012     testObject.should.equal(expectedObject);
1013   }).should.throwException!TestException.msg;
1014 
1015   msg.should.startWith(`testObject should equal {`);
1016 }
1017 
1018 /// It should find the value differences inside a Json object
1019 unittest {
1020   Json expectedObject = Json.emptyObject;
1021   Json testObject = Json.emptyObject;
1022   testObject["key1"] = "some value";
1023   testObject["key2"] = 1;
1024 
1025   expectedObject["key1"] = "other value";
1026   expectedObject["key2"] = 2;
1027 
1028   auto msg = ({
1029     testObject.should.equal(expectedObject);
1030   }).should.throwException!TestException.msg;
1031 
1032   msg.should.startWith("testObject should equal {");
1033 }
1034 
1035 /// greaterThan support for Json Objects
1036 unittest {
1037   Json(5).should.be.greaterThan(4);
1038   Json(4).should.not.be.greaterThan(5);
1039 
1040   Json(5f).should.be.greaterThan(4f);
1041   Json(4f).should.not.be.greaterThan(5f);
1042 
1043   auto msg = ({
1044     Json("").should.greaterThan(3);
1045   }).should.throwException!TestException.msg;
1046 
1047   msg.split("\n")[0].should.equal(`Json("") should greater than 3.`);
1048   msg.split("\n")[1].should.equal("Can't convert the values to int");
1049 
1050   msg = ({
1051     Json(false).should.greaterThan(3f);
1052   }).should.throwException!TestException.msg;
1053 
1054   msg.split("\n")[0].should.equal(`Json(false) should greater than 3.`);
1055   msg.split("\n")[1].should.equal("Can't convert the values to float");
1056 }
1057 
1058 /// lessThan support for Json Objects
1059 unittest {
1060   Json(4).should.be.lessThan(5);
1061   Json(5).should.not.be.lessThan(4);
1062 
1063   Json(4f).should.be.lessThan(5f);
1064   Json(5f).should.not.be.lessThan(4f);
1065 
1066   auto msg = ({
1067     Json("").should.lessThan(3);
1068   }).should.throwException!TestException.msg;
1069 
1070   msg.split("\n")[0].should.equal(`Json("") should less than 3.`);
1071   msg.split("\n")[1].should.equal(`Can't convert the values to int`);
1072 
1073   msg = ({
1074     Json(false).should.lessThan(3f);
1075   }).should.throwException!TestException.msg;
1076 
1077   msg.split("\n")[0].should.equal(`Json(false) should less than 3.`);
1078   msg.split("\n")[1].should.equal(`Can't convert the values to float`);
1079 }
1080 
1081 /// between support for Json Objects
1082 unittest {
1083   Json(5).should.be.between(6, 4);
1084   Json(5).should.not.be.between(5, 6);
1085 
1086   Json(5f).should.be.between(6f, 4f);
1087   Json(5f).should.not.be.between(5f, 6f);
1088 
1089   auto msg = ({
1090     Json(true).should.be.between(6f, 4f);
1091   }).should.throwException!TestException.msg;
1092 
1093   msg.split("\n")[0].should.equal("Json(true) should be between 6 and 4. ");
1094   msg.split("\n")[1].should.equal("Can't convert the values to float");
1095 }
1096 
1097 /// should be able to use approximately for jsons
1098 unittest {
1099   Json(10f/3f).should.be.approximately(3, 0.34);
1100   Json(10f/3f).should.not.be.approximately(3, 0.24);
1101 
1102   auto msg = ({
1103     Json("").should.be.approximately(3, 0.34);
1104   }).should.throwException!TestException.msg;
1105 
1106   msg.should.contain(`Can't parse the provided arguments!`);
1107 }