1 /**
2 * Copyright © DiamondMVC 2019
3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module diamond.controllers.basecontroller;
7
8 import diamond.core.apptype;
9
10 static if (isWeb)
11 {
12 import std.conv : to;
13 import std.variant : Variant;
14 import std.traits : EnumMembers;
15 import std.algorithm : filter;
16 import std.array : array;
17 import std..string : strip;
18
19 import diamond.http;
20 import diamond.controllers.action;
21 import diamond.controllers.status;
22 import diamond.errors;
23 import diamond.core.collections;
24 import diamond.controllers.rest;
25
26 /// Wrapper for a base controller.
27 abstract class BaseController
28 {
29 package(diamond.controllers)
30 {
31 /// Data passed by the request in a RESTful manner.
32 Variant[string] _data;
33
34 /**
35 * Valiates a route and the passed source data to it.
36 * Params:
37 * routeData = The route part data.
38 * sourceData = The data passed.
39 */
40 void validateRoute(RoutePart[] routeData, string[] sourceData)
41 {
42 if (!sourceData)
43 {
44 throw new RouteException("Passed no data to the route.");
45 }
46
47 sourceData = sourceData.filter!(d => d && d.strip().length).array;
48
49 if (sourceData.length != (routeData.length - 1))
50 {
51 throw new RouteException("Passed invalid amount of arguments to the route.");
52 }
53
54 foreach (i; 0 .. sourceData.length)
55 {
56 auto rData = routeData[i + 1];
57 auto data = sourceData[i].strip();
58
59 switch (rData.routeType)
60 {
61 case RouteType.identifier:
62 {
63 if (rData.identifier != data)
64 {
65 throw new RouteException("Expected '" ~ rData.identifier ~ "' as identifier in the route.");
66 }
67
68 break;
69 }
70
71 case RouteType.type:
72 {
73 final switch (rData.type)
74 {
75 foreach (memberIndex, member; EnumMembers!RouteDataType)
76 {
77 mixin(
78 "case RouteDataType." ~
79 to!string(cast(RouteDataType)member) ~
80 ": mapValue!" ~
81 member ~
82 "(data); break;"
83 );
84 }
85 }
86
87 break;
88 }
89
90 case RouteType.typeIdentifier:
91 {
92 final switch (rData.type)
93 {
94 foreach (memberIndex, member; EnumMembers!RouteDataType)
95 {
96 mixin(
97 "case RouteDataType." ~
98 to!string(cast(RouteDataType)member) ~
99 ": mapValue!" ~
100 member ~
101 "(data, rData.identifier); break;"
102 );
103 }
104 }
105
106 break;
107 }
108
109 default: break;
110 }
111 }
112 }
113
114 /**
115 * Maps a value that was passed to the route in a RESTful manner.
116 * Params:
117 * data = The to be mapped.
118 * mapName = The name to map it as. If no name is passed then conversion is just validated.
119 */
120 void mapValue(T)(string data, string mapName = null)
121 {
122 static if (is(typeof(T) == typeof(string)))
123 {
124 alias value = data;
125 }
126 else
127 {
128 T value = to!T(data);
129 }
130
131 if (mapName)
132 {
133 _data[mapName] = value;
134 }
135 }
136 }
137
138 protected:
139 /// Alias for the action entry.
140 alias ActionEntry = Action[string];
141 /// Alias for the method entry.
142 alias MethodEntry = ActionEntry[HttpMethod];
143
144 /// Collection of actions.
145 public MethodEntry _actions;
146 /// The default action for the controller.
147 Action _defaultAction;
148 /// The mandatory action for the controller.
149 Action _mandatoryAction;
150 /// The no-action handler for the controller.
151 Action _noAction;
152
153 /// Creates a new base controller.
154 this() { }
155
156 /// Will handle the controller.
157 public abstract Status handle();
158
159 final:
160 /**
161 * Maps an action to a http method by a name.
162 * Params:
163 * method = The http method.
164 * action = The action name.
165 * fun = The controller action associated with the mapping.
166 */
167 void mapAction(HttpMethod method, string action, Action fun)
168 {
169 _actions[method][action] = fun;
170 }
171
172 /**
173 * Maps an action to a http method by a name.
174 * Params:
175 * method = The http method.
176 * action = The action name.
177 * d = The controller action associated with the mapping.
178 */
179 void mapAction(HttpMethod method, string action, Status delegate() d)
180 {
181 _actions[method][action] = new Action(d);
182 }
183
184 /**
185 * Maps an action to a http method by a name.
186 * Params:
187 * method = The http method.
188 * action = The action name.
189 * f = The controller action associated with the mapping.
190 */
191 void mapAction(HttpMethod method, string action, Status function() f)
192 {
193 _actions[method][action] = new Action(f);
194 }
195
196 /**
197 * Maps a default action for the controller.
198 * Params:
199 * fun = The controller action associated with the mapping.
200 */
201 void mapDefault(Action fun)
202 {
203 _defaultAction = fun;
204 }
205
206 /**
207 * Maps a default action for the controller.
208 * Params:
209 * d = The controller action associated with the mapping.
210 */
211 void mapDefault(Status delegate() d)
212 {
213 _defaultAction = new Action(d);
214 }
215
216 /**
217 * Maps a default action for the controller.
218 * Params:
219 * f = The controller action associated with the mapping.
220 */
221 void mapDefault(Status function() f)
222 {
223 _defaultAction = new Action(f);
224 }
225
226 /**
227 * Maps a mandatory action for the controller.
228 * Params:
229 * fun = The controller action associated with the mapping.
230 */
231 void mapMandatory(Action fun)
232 {
233 _mandatoryAction = fun;
234 }
235
236 /**
237 * Maps a mandatory action for the controller.
238 * Params:
239 * d = The controller action associated with the mapping.
240 */
241 void mapMandatory(Status delegate() d)
242 {
243 _mandatoryAction = new Action(d);
244 }
245
246 /**
247 * Maps a mandatory action for the controller.
248 * Params:
249 * f = The controller action associated with the mapping.
250 */
251 void mapMandatory(Status function() f)
252 {
253 _mandatoryAction = new Action(f);
254 }
255
256 /**
257 * Maps a no-action for the controller.
258 * Params:
259 * fun = The controller action associated with the mapping.
260 */
261 void mapNoAction(Action fun)
262 {
263 _noAction = fun;
264 }
265
266 /**
267 * Maps a no-action for the controller.
268 * Params:
269 * d = The controller action associated with the mapping.
270 */
271 void mapNoAction(Status delegate() d)
272 {
273 _noAction = new Action(d);
274 }
275
276 /**
277 * Maps a no-action for the controller.
278 * Params:
279 * f = The controller action associated with the mapping.
280 */
281 void mapNoAction(Status function() f)
282 {
283 _noAction = new Action(f);
284 }
285
286 /**
287 * Gets a value from the passed data.
288 * Params:
289 * name = The name of the value to get.
290 * defaultValue = The default value.
291 * Returns:
292 * Returns the value if found, else the default value.
293 */
294 T get(T)(string name, T defaultValue = T.init)
295 {
296 Variant emptyVariant;
297 auto value = _data.get(name, emptyVariant);
298
299 return value.hasValue ? value.get!T : defaultValue;
300 }
301
302 static if (isTesting)
303 {
304 @property
305 {
306 import diamond.unittesting;
307
308 /// Gets a boolean determnining whether the request is a test or not.
309 bool testing() { return !testsPassed; }
310 }
311 }
312 }
313 }