1 module dmocks.action; 2 3 import dmocks.dynamic; 4 import dmocks.util; 5 import std.meta : AliasSeq; 6 import std.traits; 7 import std.typecons; 8 9 package: 10 11 struct ReturnOrPass(T) 12 { 13 static if (!is(T == void)) 14 { 15 T value = void; 16 17 this(T value, Flag!"pass" pass) 18 { 19 this.value = value; 20 this.pass = pass; 21 } 22 } 23 24 this(Flag!"pass" pass) 25 { 26 this.pass = pass; 27 } 28 29 Flag!"pass" pass; 30 } 31 32 struct Actor 33 { 34 Action self; 35 36 ReturnOrPass!(TReturn) act(TReturn, ArgTypes...)(ArgTypes args) 37 { 38 debugLog("Actor:act"); 39 40 alias Rope = ReturnOrPass!TReturn; 41 42 if (self.passThrough) 43 { 44 return Rope(Yes.pass); 45 } 46 if (self.toThrow) 47 { 48 throw self.toThrow; 49 } 50 static if (is(TReturn == void)) 51 { 52 if (self.action is null) 53 { 54 return Rope(No.pass); 55 } 56 debugLog("action found, type: %s", self.action.typename); 57 58 if (self.action.type == typeid(void delegate(ArgTypes))) 59 { 60 self.action.get!(void delegate(ArgTypes))()(args); 61 return Rope(No.pass); 62 } 63 else 64 { 65 import std.format : format; 66 67 throw new Error(format!"cannot call action: type %s does not match argument type %s"( 68 self.action.typename, ArgTypes.stringof)); 69 } 70 } 71 else 72 { 73 if (self.returnValue !is null) 74 { 75 return Rope(self.returnValue.get!TReturn, No.pass); 76 } 77 else if (self.action !is null) 78 { 79 debugLog("action found, type: %s", self.action.typename); 80 if (self.action.type == typeid(TReturn delegate(ArgTypes))) 81 { 82 return Rope(self.action.get!(TReturn delegate(ArgTypes))()(args), No.pass); 83 } 84 else 85 { 86 import std.format : format; 87 88 throw new Error(format!"cannot call action: type %s does not match argument type %s"( 89 self.action.typename, ArgTypes.stringof)); 90 } 91 } 92 return Rope(No.pass); 93 } 94 } 95 } 96 97 //TODO: make action parameters orthogonal or disallow certain combinations of them 98 class Action 99 { 100 bool passThrough; 101 102 private Dynamic _returnValue; 103 104 Dynamic action; 105 106 Exception toThrow; 107 108 private TypeInfo _returnType; 109 110 this(TypeInfo returnType) 111 { 112 this._returnType = returnType; 113 } 114 115 bool hasAction() 116 { 117 return (_returnType is typeid(void)) || (passThrough) || 118 (_returnValue !is null) || (action !is null) || (toThrow !is null); 119 } 120 121 Actor getActor() 122 { 123 Actor actor; 124 125 actor.self = this; 126 return actor; 127 } 128 129 @property Dynamic returnValue() 130 { 131 return this._returnValue; 132 } 133 134 @property void returnValue(Dynamic dynamic) 135 { 136 import std.exception : enforce; 137 import std.format : format; 138 139 enforce( 140 dynamic.type == this._returnType || dynamic.canConvertTo(this._returnType), 141 format!"Cannot set return value to '%s': expected '%s'"(dynamic.typename, this._returnType)); 142 143 this._returnValue = dynamic; 144 } 145 } 146 147 @("action returnValue") 148 unittest 149 { 150 Dynamic v = dynamic(5); 151 Action act = new Action(typeid(int)); 152 assert(act.returnValue is null); 153 act.returnValue = v; 154 assert(act.returnValue == dynamic(5)); 155 } 156 157 @("action throws on mismatching returnValue") 158 unittest 159 { 160 import std.exception : assertThrown; 161 162 Dynamic v = dynamic(5.0f); 163 Action act = new Action(typeid(int)); 164 165 assert(act.returnValue is null); 166 assertThrown!Exception(act.returnValue = v); 167 } 168 169 private interface ExampleInterface 170 { 171 } 172 173 static foreach (Type; AliasSeq!(string, int*, Object, ExampleInterface, void function(), void delegate())) 174 { 175 @("action accepts null returnValue for " ~ Type.stringof) 176 unittest 177 { 178 Action act = new Action(typeid(Type)); 179 180 act.returnValue = dynamic(null); 181 assert(act.returnValue.get!Type is null); 182 } 183 } 184 185 @("action action") 186 unittest 187 { 188 Dynamic v = dynamic(5); 189 Action act = new Action(typeid(int)); 190 assert(act.action is null); 191 act.action = v; 192 assert(act.action == v); 193 } 194 195 @("action exception") 196 unittest 197 { 198 Exception ex = new Exception("boogah"); 199 Action act = new Action(typeid(int)); 200 assert(act.toThrow is null); 201 act.toThrow = ex; 202 assert(act.toThrow is ex); 203 } 204 205 @("action passthrough") 206 unittest 207 { 208 Action act = new Action(typeid(int)); 209 act.passThrough = true; 210 assert(act.passThrough); 211 act.passThrough = false; 212 assert(!act.passThrough); 213 } 214 215 @("action hasAction") 216 unittest 217 { 218 Action act = new Action(typeid(int)); 219 act.returnValue = dynamic(5); 220 assert(act.hasAction); 221 }