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 }