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 }