1 module fluentasserts.core.operations.registry;
2 
3 import fluentasserts.core.results;
4 import fluentasserts.core.evaluation;
5 
6 import std.functional;
7 import std..string;
8 
9 /// Delegate type that can handle asserts
10 alias Operation = IResult[] delegate(ref Evaluation) @safe nothrow;
11 
12 /// ditto
13 alias OperationFunc = IResult[] delegate(ref Evaluation) @safe nothrow;
14 
15 ///
16 class Registry {
17   /// Global instance for the assert operations
18   static Registry instance;
19 
20   private {
21     Operation[string] operations;
22   }
23 
24   /// Register a new assert operation
25   Registry register(T, U)(string name, Operation operation) {
26     foreach(valueType; extractTypes!T) {
27       foreach(expectedValueType; extractTypes!U) {
28         register(valueType, expectedValueType, name, operation);
29       }
30     }
31 
32     return this;
33   }
34 
35   /// ditto
36   Registry register(T, U)(string name, IResult[] function(ref Evaluation) @safe nothrow operation) {
37     const operationDelegate = operation.toDelegate;
38     return this.register!(T, U)(name, operationDelegate);
39   }
40 
41   /// ditto
42   Registry register(string valueType, string expectedValueType, string name, Operation operation) {
43     string key = valueType ~ "." ~ expectedValueType ~ "." ~ name;
44 
45     operations[key] = operation;
46 
47     return this;
48   }
49 
50   /// ditto
51   Registry register(string valueType, string expectedValueType, string name, IResult[] function(ref Evaluation) @safe nothrow operation) {
52     return this.register(valueType, expectedValueType, name, operation.toDelegate);
53   }
54 
55   /// Get an operation function
56   Operation get(string valueType, string expectedValueType, string name) @safe nothrow {
57     assert(valueType != "", "The value type is not set!");
58     assert(name != "", "The operation name is not set!");
59 
60     auto genericKeys = [valueType ~ "." ~ expectedValueType ~ "." ~ name] ~ generalizeKey(valueType, expectedValueType, name);
61     string matchedKey;
62 
63     foreach(key; genericKeys) {
64       if(key in operations) {
65         matchedKey = key;
66         break;
67       }
68     }
69 
70     assert(matchedKey != "", "There are no matching assert operations. Register any of `" ~ genericKeys.join("`, `") ~ "` to perform this assert.");
71 
72     return operations[matchedKey];
73   }
74 
75   ///
76   IResult[] handle(ref Evaluation evaluation) @safe nothrow {
77     auto operation = this.get(
78       evaluation.currentValue.typeName,
79       evaluation.expectedValue.typeName,
80       evaluation.operationName);
81 
82     return operation(evaluation);
83   }
84 }
85 
86 string[] generalizeKey(string valueType, string expectedValueType, string name) @safe nothrow {
87   string[] results;
88 
89   foreach (string generalizedValueType; generalizeType(valueType)) {
90     foreach (string generalizedExpectedValueType; generalizeType(expectedValueType)) {
91       results ~=  generalizedValueType ~ "." ~ generalizedExpectedValueType ~ "." ~ name;
92     }
93   }
94 
95   return  results;
96 }
97 
98 string[] generalizeType(string typeName) @safe nothrow {
99   auto pos = typeName.indexOf("[");
100   if(pos == -1) {
101     return ["*"];
102   }
103 
104   string[] results = [];
105 
106   const pieces = typeName.split("[");
107 
108   string arrayType;
109   bool isHashMap;
110   int index = 0;
111   int diff = 0;
112 
113   foreach (ch; typeName[pos..$]) {
114     diff++;
115     if(ch == '[') {
116       index++;
117     }
118 
119     if(ch == ']') {
120       index--;
121     }
122 
123     if(index == 0 && diff == 2) {
124       arrayType ~= "[]";
125     }
126 
127     if(index == 0 && diff != 2) {
128       arrayType ~= "[*]";
129       isHashMap = true;
130     }
131 
132     if(index == 0) {
133       diff = 0;
134     }
135   }
136 
137   if(isHashMap) {
138     results ~= "*" ~ typeName[pos..$];
139     results ~= pieces[0] ~ arrayType;
140   }
141 
142   results ~= "*" ~ arrayType;
143 
144   return results;
145 }
146 
147 version(unittest) {
148   import fluentasserts.core.base;
149 }
150 
151 /// It can generalize an int
152 unittest {
153   generalizeType("int").should.equal(["*"]);
154 }
155 
156 /// It can generalize a list
157 unittest {
158   generalizeType("int[]").should.equal(["*[]"]);
159 }
160 
161 /// It can generalize a list of lists
162 unittest {
163   generalizeType("int[][]").should.equal(["*[][]"]);
164 }
165 
166 /// It can generalize an assoc array
167 unittest {
168   generalizeType("int[int]").should.equal(["*[int]", "int[*]", "*[*]"]);
169 }
170 
171 /// It can generalize a combination of assoc arrays and lists
172 unittest {
173   generalizeType("int[int][][string][]").should.equal(["*[int][][string][]", "int[*][][*][]", "*[*][][*][]"]);
174 }
175 
176 /// It can generalize an assoc array with a key list
177 unittest {
178   generalizeType("int[int[]]").should.equal(["*[int[]]", "int[*]", "*[*]"]);
179 }