1 /** 2 * Copyright © DiamondMVC 2018 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 151 /// Creates a new base controller. 152 this() { } 153 154 /// Will handle the controller. 155 public abstract Status handle(); 156 157 final: 158 /** 159 * Maps an action to a http method by a name. 160 * Params: 161 * method = The http method. 162 * action = The action name. 163 * fun = The controller action associated with the mapping. 164 */ 165 void mapAction(HttpMethod method, string action, Action fun) 166 { 167 _actions[method][action] = fun; 168 } 169 170 /** 171 * Maps an action to a http method by a name. 172 * Params: 173 * method = The http method. 174 * action = The action name. 175 * d = The controller action associated with the mapping. 176 */ 177 void mapAction(HttpMethod method, string action, Status delegate() d) 178 { 179 _actions[method][action] = new Action(d); 180 } 181 182 /** 183 * Maps an action to a http method by a name. 184 * Params: 185 * method = The http method. 186 * action = The action name. 187 * f = The controller action associated with the mapping. 188 */ 189 void mapAction(HttpMethod method, string action, Status function() f) 190 { 191 _actions[method][action] = new Action(f); 192 } 193 194 /** 195 * Maps a default action for the controller. 196 * Params: 197 * fun = The controller action associated with the mapping. 198 */ 199 void mapDefault(Action fun) 200 { 201 _defaultAction = fun; 202 } 203 204 /** 205 * Maps a default action for the controller. 206 * Params: 207 * d = The controller action associated with the mapping. 208 */ 209 void mapDefault(Status delegate() d) 210 { 211 _defaultAction = new Action(d); 212 } 213 214 /** 215 * Maps a default action for the controller. 216 * Params: 217 * f = The controller action associated with the mapping. 218 */ 219 void mapDefault(Status function() f) 220 { 221 _defaultAction = new Action(f); 222 } 223 224 /** 225 * Maps a mandatory action for the controller. 226 * Params: 227 * fun = The controller action associated with the mapping. 228 */ 229 void mapMandatory(Action fun) 230 { 231 _mandatoryAction = fun; 232 } 233 234 /** 235 * Maps a mandatory action for the controller. 236 * Params: 237 * d = The controller action associated with the mapping. 238 */ 239 void mapMandatory(Status delegate() d) 240 { 241 _mandatoryAction = new Action(d); 242 } 243 244 /** 245 * Maps a mandatory action for the controller. 246 * Params: 247 * f = The controller action associated with the mapping. 248 */ 249 void mapMandatory(Status function() f) 250 { 251 _mandatoryAction = new Action(f); 252 } 253 254 /** 255 * Gets a value from the passed data. 256 * Params: 257 * name = The name of the value to get. 258 * defaultValue = The default value. 259 * Returns: 260 * Returns the value if found, else the default value. 261 */ 262 T get(T)(string name, T defaultValue = T.init) 263 { 264 Variant emptyVariant; 265 auto value = _data.get(name, emptyVariant); 266 267 return value.hasValue ? value.get!T : defaultValue; 268 } 269 270 static if (isTesting) 271 { 272 @property 273 { 274 import diamond.unittesting; 275 276 /// Gets a boolean determnining whether the request is a test or not. 277 bool testing() { return !testsPassed; } 278 } 279 } 280 } 281 }