fluentasserts.core.serializers 241/243(99%) line coverage

      
10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
160
170
180
190
200
210
220
230
240
250
260
270
286
296
300
310
322
330
340
3514
3614
370
380
394
400
410
420
430
440
450
460
470
480
490
500
510
520
530
540
550
560
570
5824
590
608963
610
620
630
640
650
6637
670
68136
690
700
710
720
73920
74920
750
760
77897
786
790
800
810
820
8323
8414
850
860
870
880
892
900
910
920
930
94900
950
960
97680
9812
990
100668
101668
1020
1030
104102
1050
10693
1070
10825
1090
1100
111900
1125
1130
1145
1155
1160
1170
118900
1191
1201
1211
1220
1230
124900
1250
1260
1270
1280
1290
13011129
1310
132165
1330
1349492
1350
1360
1370
1380
1390
1403
1413
1420
1430
1440
1450
1460
1470
1480
1490
15054
1510
15262
1530
1548138
1550
1560
1570
1580
1590
1600
1610
1620
1630
1643
1650
1661
1671
1680
1692
1702
1712
1720
1730
1740
1750
1760
1770
1780
1793
1800
1811
1821
1830
1841
1850
1862
1872
1882
1890
1900
1910
1920
1930
1940
1950
1964
1970
1981
1991
2000
2011
2021
2031
2040
2052
2062
2072
2082
2092
2102
2110
2120
2130
2140
2150
2160
2170
2180
2193
2200
2211
2221
2230
2242
2252
2262
2270
2280
2290
2300
2310
2320
2330
2343
2350
2361
2371
2380
2391
2400
2412
2422
2432
2440
2450
2460
2470
2480
2490
2500
2514
2520
2531
2541
2550
2561
2571
2581
2590
2602
2612
2622
2632
2642
2652
2660
2670
2680
2690
2701
2711
2721
2730
2742
2752
2762
2770
2780
2790
2800
2811
2821
2831
2840
2852
2862
2872
2880
2890
2900
2910
2921
2931
2941
2950
2962
2972
2982
2990
3000
3010
3020
3031
3041
3051
3060
3072
3082
3092
3100
3110
3120
3130
3141
3151
3161
3170
3182
3192
3202
3210
3220
3230
3240
3251
3261
3271
3280
3292
3302
3312
3320
3330
3340
3350
3361
3371
3381
3390
3402
3412
3422
3430
3440
3450
3460
3471
3481
3491
3500
3512
3522
3532
3540
3550
3560
3570
3580
3590
3600
3610
3621
3631
3641
3650
3662
3672
3682
3690
3700
3710
3720
3730
3741
3751
3761
3770
3782
3792
3802
3810
3820
3830
3840
3850
3860
3870
3880
3890
3900
3910
3920
393363
3940
3955805
3960
3970
3980
3990
4000
4010
4020
4030
4040
4050
4060
4070
4080
4090
4100
4110
4120
4130
4140
4150
4160
4170
4180
4190
4200
4210
4220
4230
4240
4250
426225
4271
4280
4290
430224
4311
4320
4330
434333
435113
4360
4370
438110
439110
4400
441110
442110
443110
444110
4450
4464959
4471543
4483847
4490
4502768
451139
452139
453139
4540
4550
4562796
4571007
45815
45915
4600
4610
4621007
46315
4640
46515
46610
4670
4680
4690
4700
4711404
4722676
473126
4740
4750
4762303
47712
4780
4790
4800
4811404
4820
4830
484110
485107
4860
4870
488110
4890
4900
4910
4920
4931
4940
4952
4960
4970
4980
4990
5001
5010
5022
5030
5040
5050
5060
5070
5081
5090
5102
5110
5120
5130
5140
5151
5160
5172
5180
5190
5200
5210
5221
5230
5242
5250
5260
5270
5280
5291
5300
5312
5320
5330
5340
5350
5361
5370
5382
5390
5400
5410
5420
5431
5440
5452
5460
5470
5480
5490
5501
5510
5522
5530
5540
5550
5560
5571
5580
5592
5600
5610
5620
5630
5641
5650
5662
5670
5680
5690
5700
5711
5720
5732
5740
5750
5760
5770
5781
5790
5802
5810
5820
5830
5840
5851
5862
5870
5880
5890
5900
5911
5922
5930
5940
5950
5960
5971
5982
5990
6000
6010
6020
6031
6042
6050
6060
6070
6080
6091
6102
6110
6120
6130
6140
6151538
61680
6170
6180
6191458
6201458
6210
6222132
623607
6240
6250
6260
627851
6280
6290
6300
6310
6322
6330
6340
6350
6360
6372
6380
6390
6400
6410
6422
6430
6440
6450
6460
6472
6480
6490
6500
6510
652542
6530
6540
6550
6560
6571
6580
6592
6600
6610
6620
6630
6642
6650
module fluentasserts.core.serializers; import std.array; import std.string; import std.algorithm; import std.traits; import std.conv; import std.datetime; import std.functional; version(unittest) import fluent.asserts; /// Singleton used to serialize to string the tested values class SerializerRegistry { static SerializerRegistry instance; private { string delegate(void*)[string] serializers; string delegate(const void*)[string] constSerializers; string delegate(immutable void*)[string] immutableSerializers; } /// void register(T)(string delegate(T) serializer) if(isAggregateType!T) { enum key = T.stringof; static if(is(Unqual!T == T)) { string wrap(void* val) { auto value = (cast(T*) val); return serializer(*value); } serializers[key] = &wrap; } else static if(is(ConstOf!T == T)) { string wrap(const void* val) { auto value = (cast(T*) val); return serializer(*value); } constSerializers[key] = &wrap; } else static if(is(ImmutableOf!T == T)) { string wrap(immutable void* val) { auto value = (cast(T*) val); return serializer(*value); } immutableSerializers[key] = &wrap; } } void register(T)(string function(T) serializer) { auto serializerDelegate = serializer.toDelegate; this.register(serializerDelegate); } /// string serialize(T)(T[] value) if(!isSomeString!(T[])) { static if(is(Unqual!T == void)) { return "[]"; } else { return "[" ~ value.map!(a => serialize(a)).joiner(", ").array.to!string ~ "]"; } } /// string serialize(T: V[K], V, K)(T value) { auto keys = value.byKey.array.sort; return "[" ~ keys.map!(a => serialize(a) ~ ":" ~ serialize(value[a])).joiner(", ").array.to!string ~ "]"; } /// string serialize(T)(T value) if(isAggregateType!T) { auto key = T.stringof; auto tmp = &value; static if(is(Unqual!T == T)) { if(key in serializers) { return serializers[key](tmp); } } static if(is(ConstOf!T == T)) { if(key in constSerializers) { return constSerializers[key](tmp); } } static if(is(ImmutableOf!T == T)) { if(key in immutableSerializers) { return immutableSerializers[key](tmp); } } string result; static if(is(T == class)) { if(value is null) { result = "null"; } else { auto v = (cast() value); result = T.stringof ~ "(" ~ v.toHash.to!string ~ ")"; } } else static if(is(Unqual!T == Duration)) { result = value.total!"nsecs".to!string; } else static if(is(Unqual!T == SysTime)) { result = value.toISOExtString; } else { result = value.to!string; } if(result.indexOf("const(") == 0) { result = result[6..$]; auto pos = result.indexOf(")"); result = result[0..pos] ~ result[pos + 1..$]; } if(result.indexOf("immutable(") == 0) { result = result[10..$]; auto pos = result.indexOf(")"); result = result[0..pos] ~ result[pos + 1..$]; } return result; } /// string serialize(T)(T value) if(!is(T == enum) && (isSomeString!T || (!isArray!T && !isAssociativeArray!T && !isAggregateType!T))) { static if(isSomeString!T) { return `"` ~ value.to!string ~ `"`; } else static if(isSomeChar!T) { return `'` ~ value.to!string ~ `'`; } else { return value.to!string; } } string serialize(T)(T value) if(is(T == enum)) { static foreach(member; EnumMembers!T) { if(member == value) { return this.serialize(cast(OriginalType!T) member); } } throw new Exception("The value can not be serialized."); } string niceValue(T)(T value) { static if(is(Unqual!T == SysTime)) { return value.toISOExtString; } else static if(is(Unqual!T == Duration)) { return value.to!string; } else { return serialize(value); } } } /// It should be able to override the default struct serializer unittest { struct A {} string serializer(A) { return "custom value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); registry.serialize(A()).should.equal("custom value"); registry.serialize([A()]).should.equal("[custom value]"); registry.serialize(["key": A()]).should.equal(`["key":custom value]`); } /// It should be able to override the default const struct serializer unittest { struct A {} string serializer(const A) { return "custom value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); const A value; registry.serialize(value).should.equal("custom value"); registry.serialize([value]).should.equal("[custom value]"); registry.serialize(["key": value]).should.equal(`["key":custom value]`); } /// It should be able to override the default immutable struct serializer unittest { struct A {} string serializer(immutable A) { return "value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); immutable A ivalue; const A cvalue; A value; registry.serialize(value).should.equal("A()"); registry.serialize(cvalue).should.equal("A()"); registry.serialize(ivalue).should.equal("value"); registry.serialize(ivalue).should.equal("value"); registry.serialize([ivalue]).should.equal("[value]"); registry.serialize(["key": ivalue]).should.equal(`["key":value]`); } /// It should be able to override the default class serializer unittest { class A {} string serializer(A) { return "custom value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); registry.serialize(new A()).should.equal("custom value"); registry.serialize([new A()]).should.equal("[custom value]"); registry.serialize(["key": new A()]).should.equal(`["key":custom value]`); } /// It should be able to override the default const class serializer unittest { class A {} string serializer(const A) { return "custom value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); const A value = new A; registry.serialize(value).should.equal("custom value"); registry.serialize([value]).should.equal("[custom value]"); registry.serialize(["key": value]).should.equal(`["key":custom value]`); } /// It should be able to override the default immutable class serializer unittest { class A {} string serializer(immutable A) { return "value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); immutable A ivalue; const A cvalue; A value; registry.serialize(value).should.equal("null"); registry.serialize(cvalue).should.equal("null"); registry.serialize(ivalue).should.equal("value"); registry.serialize(ivalue).should.equal("value"); registry.serialize([ivalue]).should.equal("[value]"); registry.serialize(["key": ivalue]).should.equal(`["key":value]`); } /// It should serialize a char unittest { char ch = 'a'; const char cch = 'a'; immutable char ich = 'a'; SerializerRegistry.instance.serialize(ch).should.equal("'a'"); SerializerRegistry.instance.serialize(cch).should.equal("'a'"); SerializerRegistry.instance.serialize(ich).should.equal("'a'"); } /// It should serialize a SysTime unittest { SysTime val = SysTime.fromISOExtString("2010-07-04T07:06:12"); const SysTime cval = SysTime.fromISOExtString("2010-07-04T07:06:12"); immutable SysTime ival = SysTime.fromISOExtString("2010-07-04T07:06:12"); SerializerRegistry.instance.serialize(val).should.equal("2010-07-04T07:06:12"); SerializerRegistry.instance.serialize(cval).should.equal("2010-07-04T07:06:12"); SerializerRegistry.instance.serialize(ival).should.equal("2010-07-04T07:06:12"); } /// It should serialize a string unittest { string str = "aaa"; const string cstr = "aaa"; immutable string istr = "aaa"; SerializerRegistry.instance.serialize(str).should.equal(`"aaa"`); SerializerRegistry.instance.serialize(cstr).should.equal(`"aaa"`); SerializerRegistry.instance.serialize(istr).should.equal(`"aaa"`); } /// It should serialize an int unittest { int value = 23; const int cvalue = 23; immutable int ivalue = 23; SerializerRegistry.instance.serialize(value).should.equal(`23`); SerializerRegistry.instance.serialize(cvalue).should.equal(`23`); SerializerRegistry.instance.serialize(ivalue).should.equal(`23`); } /// It should serialize an int list unittest { int[] value = [2,3]; const int[] cvalue = [2,3]; immutable int[] ivalue = [2,3]; SerializerRegistry.instance.serialize(value).should.equal(`[2, 3]`); SerializerRegistry.instance.serialize(cvalue).should.equal(`[2, 3]`); SerializerRegistry.instance.serialize(ivalue).should.equal(`[2, 3]`); } /// It should serialize a void list unittest { void[] value = []; const void[] cvalue = []; immutable void[] ivalue = []; SerializerRegistry.instance.serialize(value).should.equal(`[]`); SerializerRegistry.instance.serialize(cvalue).should.equal(`[]`); SerializerRegistry.instance.serialize(ivalue).should.equal(`[]`); } /// It should serialize a nested int list unittest { int[][] value = [[0,1],[2,3]]; const int[][] cvalue = [[0,1],[2,3]]; immutable int[][] ivalue = [[0,1],[2,3]]; SerializerRegistry.instance.serialize(value).should.equal(`[[0, 1], [2, 3]]`); SerializerRegistry.instance.serialize(cvalue).should.equal(`[[0, 1], [2, 3]]`); SerializerRegistry.instance.serialize(ivalue).should.equal(`[[0, 1], [2, 3]]`); } /// It should serialize an assoc array unittest { int[string] value = ["a": 2,"b": 3, "c": 4]; const int[string] cvalue = ["a": 2,"b": 3, "c": 4]; immutable int[string] ivalue = ["a": 2,"b": 3, "c": 4]; SerializerRegistry.instance.serialize(value).should.equal(`["a":2, "b":3, "c":4]`); SerializerRegistry.instance.serialize(cvalue).should.equal(`["a":2, "b":3, "c":4]`); SerializerRegistry.instance.serialize(ivalue).should.equal(`["a":2, "b":3, "c":4]`); } /// It should serialize a string enum unittest { enum TestType : string { a = "a", b = "b" } TestType value = TestType.a; const TestType cvalue = TestType.a; immutable TestType ivalue = TestType.a; SerializerRegistry.instance.serialize(value).should.equal(`"a"`); SerializerRegistry.instance.serialize(cvalue).should.equal(`"a"`); SerializerRegistry.instance.serialize(ivalue).should.equal(`"a"`); } version(unittest) { struct TestStruct { int a; string b; }; } /// It should serialize a struct unittest { TestStruct value = TestStruct(1, "2"); const TestStruct cvalue = TestStruct(1, "2"); immutable TestStruct ivalue = TestStruct(1, "2"); SerializerRegistry.instance.serialize(value).should.equal(`TestStruct(1, "2")`); SerializerRegistry.instance.serialize(cvalue).should.equal(`TestStruct(1, "2")`); SerializerRegistry.instance.serialize(ivalue).should.equal(`TestStruct(1, "2")`); } string unqualString(T: U[], U)() if(isArray!T && !isSomeString!T) { return unqualString!U ~ "[]"; } string unqualString(T: V[K], V, K)() if(isAssociativeArray!T) { return unqualString!V ~ "[" ~ unqualString!K ~ "]"; } string unqualString(T)() if(isSomeString!T || (!isArray!T && !isAssociativeArray!T)) { static if(is(T == class) || is(T == struct) || is(T == interface)) { return fullyQualifiedName!(Unqual!(T)); } else { return Unqual!T.stringof; } } string joinClassTypes(T)() { string result; static if(is(T == class)) { static foreach(Type; BaseClassesTuple!T) { result ~= Type.stringof; } } static if(is(T == interface) || is(T == class)) { static foreach(Type; InterfacesTuple!T) { if(result.length > 0) result ~= ":"; result ~= Type.stringof; } } static if(!is(T == interface) && !is(T == class)) { result = Unqual!T.stringof; } return result; } /// string[] parseList(string value) @safe nothrow { if(value.length == 0) { return []; } if(value.length == 1) { return [ value ]; } if(value[0] != '[' || value[value.length - 1] != ']') { return [ value ]; } string[] result; string currentValue; bool isInsideString; bool isInsideChar; bool isInsideArray; long arrayIndex = 0; foreach(index; 1..value.length - 1) { auto ch = value[index]; auto canSplit = !isInsideString && !isInsideChar && !isInsideArray; if(canSplit && ch == ',' && currentValue.length > 0) { result ~= currentValue.strip.dup; currentValue = ""; continue; } if(!isInsideChar && !isInsideString) { if(ch == '[') { arrayIndex++; isInsideArray = true; } if(ch == ']') { arrayIndex--; if(arrayIndex == 0) { isInsideArray = false; } } } if(!isInsideArray) { if(!isInsideChar && ch == '"') { isInsideString = !isInsideString; } if(!isInsideString && ch == '\'') { isInsideChar = !isInsideChar; } } currentValue ~= ch; } if(currentValue.length > 0) { result ~= currentValue.strip; } return result; } /// it should parse an empty string unittest { auto pieces = "".parseList; pieces.should.equal([]); } /// it should not parse a string that does not contain [] unittest { auto pieces = "test".parseList; pieces.should.equal([ "test" ]); } /// it should not parse a char that does not contain [] unittest { auto pieces = "t".parseList; pieces.should.equal([ "t" ]); } /// it should parse an empty array unittest { auto pieces = "[]".parseList; pieces.should.equal([]); } /// it should parse a list of one number unittest { auto pieces = "[1]".parseList; pieces.should.equal(["1"]); } /// it should parse a list of two numbers unittest { auto pieces = "[1,2]".parseList; pieces.should.equal(["1","2"]); } /// it should remove the whitespaces from the parsed values unittest { auto pieces = "[ 1, 2 ]".parseList; pieces.should.equal(["1","2"]); } /// it should parse two string values that contain a `,` unittest { auto pieces = `[ "a,b", "c,d" ]`.parseList; pieces.should.equal([`"a,b"`,`"c,d"`]); } /// it should parse two string values that contain a `'` unittest { auto pieces = `[ "a'b", "c'd" ]`.parseList; pieces.should.equal([`"a'b"`,`"c'd"`]); } /// it should parse two char values that contain a `,` unittest { auto pieces = `[ ',' , ',' ]`.parseList; pieces.should.equal([`','`,`','`]); } /// it should parse two char values that contain `[` and `]` unittest { auto pieces = `[ '[' , ']' ]`.parseList; pieces.should.equal([`'['`,`']'`]); } /// it should parse two string values that contain `[` and `]` unittest { auto pieces = `[ "[" , "]" ]`.parseList; pieces.should.equal([`"["`,`"]"`]); } /// it should parse two char values that contain a `"` unittest { auto pieces = `[ '"' , '"' ]`.parseList; pieces.should.equal([`'"'`,`'"'`]); } /// it should parse two empty lists unittest { auto pieces = `[ [] , [] ]`.parseList; pieces.should.equal([`[]`,`[]`]); } /// it should parse two nested lists unittest { auto pieces = `[ [[],[]] , [[[]],[]] ]`.parseList; pieces.should.equal([`[[],[]]`,`[[[]],[]]`]); } /// it should parse two lists with items unittest { auto pieces = `[ [1,2] , [3,4] ]`.parseList; pieces.should.equal([`[1,2]`,`[3,4]`]); } /// it should parse two lists with string and char items unittest { auto pieces = `[ ["1", "2"] , ['3', '4'] ]`.parseList; pieces.should.equal([`["1", "2"]`,`['3', '4']`]); } /// it should parse two lists with string and char items unittest { auto pieces = `[ ["1", "2"] , ['3', '4'] ]`.parseList; pieces.should.equal([`["1", "2"]`,`['3', '4']`]); } /// string cleanString(string value) @safe nothrow { if(value.length <= 1) { return value; } char first = value[0]; char last = value[value.length - 1]; if(first == last && (first == '"' || first == '\'')) { return value[1..$-1]; } return value; } /// it should return an empty string when the input is an empty string unittest { "".cleanString.should.equal(""); } /// it should return the input value when it has one char unittest { "'".cleanString.should.equal("'"); } /// it should remove the " from start and end of the string unittest { `""`.cleanString.should.equal(``); } /// it should remove the ' from start and end of the string unittest { `''`.cleanString.should.equal(``); } /// string[] cleanString(string[] pieces) @safe nothrow { return pieces.map!(a => a.cleanString).array; } /// It should return an empty array when the input list is empty unittest { string[] empty; empty.cleanString.should.equal(empty); } /// It should remove the `"` from the begin and end of the string unittest { [`"1"`, `"2"`].cleanString.should.equal([`1`, `2`]); }