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 }