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 }