1 /** 2 * Copyright © Yurai Web Framework 2021 3 * License: MIT (https://github.com/YuraiWeb/yurai/blob/main/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 */ 6 module yurai.controllers.controllerimplementations; 7 8 import std.string : strip, format; 9 import std.traits : fullyQualifiedName, hasUDA, getUDAs, Parameters, ParameterIdentifierTuple; 10 import std.array : split, array, join; 11 import std.conv : to; 12 13 import yurai.core; 14 import yurai.controllers.controller; 15 import yurai.controllers.controlleraction; 16 import yurai.controllers.controlleractionset; 17 import yurai.controllers.status; 18 import yurai.controllers.httpattributes; 19 20 enum memberHttpMethodFormat = q{ 21 static if (hasUDA!(%1$s.%2$s, HttpPost)) 22 { 23 static const memberHttpMethod = "POST"; 24 } 25 else static if (hasUDA!(%1$s.%2$s, HttpPut)) 26 { 27 static const memberHttpMethod = "PUT"; 28 } 29 else static if (hasUDA!(%1$s.%2$s, HttpDelete)) 30 { 31 static const memberHttpMethod = "DELETE"; 32 } 33 else static if (hasUDA!(%1$s.%2$s, HttpPatch)) 34 { 35 static const memberHttpMethod = "PATCH"; 36 } 37 else static if (hasUDA!(%1$s.%2$s, HttpOptions)) 38 { 39 static const memberHttpMethod = "OPTIONS"; 40 } 41 else static if (hasUDA!(%1$s.%2$s, HttpHead)) 42 { 43 static const memberHttpMethod = "HEAD"; 44 } 45 else static if (hasUDA!(%1$s.%2$s, HttpConnect)) 46 { 47 static const memberHttpMethod = "CONNECT"; 48 } 49 else static if (hasUDA!(%1$s.%2$s, HttpTrace)) 50 { 51 static const memberHttpMethod = "TRACE"; 52 } 53 else 54 { 55 static const memberHttpMethod = "GET"; 56 } 57 }; 58 59 enum memberNameFormat = q{ 60 static if (hasUDA!(%1$s.%2$s, HttpRoute)) 61 { 62 enum httpRoute = getUDAs!(%1$s.%2$s, HttpRoute)[0]; 63 64 static const memberActionName = httpRoute.name; 65 } 66 else 67 { 68 static const memberActionName = "%2$s"; 69 } 70 }; 71 72 enum defaultMappingFormat = q{ 73 static if (hasUDA!(%1$s.%2$s, HttpDefault)) 74 { 75 mapDefaultAction(memberHttpMethod, new ControllerAction(&controller.%2$s)); 76 } 77 }; 78 79 enum mandatoryMappingFormat = q{ 80 static if (hasUDA!(%1$s.%2$s, HttpMandatory)) 81 { 82 mapMandatoryAction(memberHttpMethod, new ControllerAction(&controller.%2$s)); 83 } 84 }; 85 86 enum actionMappingFormat = q{ 87 static if (!(hasUDA!(%1$s.%2$s, HttpIgnore))) 88 { 89 enum parameterTypes_%2$s = Parameters!(controller.%2$s).stringof[1..$-1].split(", "); 90 91 static if (parameterTypes_%2$s.length) 92 { 93 template isJsonObject(T) 94 { 95 static if (is(T == struct) || is(T == class)) 96 { 97 enum isJsonObject = true; 98 } 99 else 100 { 101 enum isJsonObject = false; 102 } 103 } 104 105 mixin("static const isJson = isJsonObject!" ~ parameterTypes_%2$s[0] ~ ";"); 106 107 static const isQuery = hasUDA!(%1$s.%2$s, HttpQuery); 108 static const isForm = hasUDA!(%1$s.%2$s, HttpForm); 109 static const isPath = hasUDA!(%1$s.%2$s, HttpPath); 110 111 static if (isJson) 112 { 113 static if(parameterTypes_%2$s.length == 1) 114 { 115 mapRoutedAction(memberHttpMethod, memberActionName, 116 new ControllerAction({ 117 mixin("auto jsonResult = deserializeJson!("~ parameterTypes_%2$s[0] ~")(request.textBody);"); 118 return controller.%2$s(jsonResult); 119 })); 120 } 121 else 122 { 123 static assert(0, "Can only map a single json object."); 124 } 125 } 126 else static if (isQuery || isForm || isPath) 127 { 128 enum parameterNames_%2$s = [ParameterIdentifierTuple!(controller.%2$s)]; 129 130 mapRoutedAction(memberHttpMethod, memberActionName, 131 new ControllerAction({ 132 static foreach (i; 0 .. parameterNames_%2$s.length) 133 { 134 static if (isQuery) 135 { 136 mixin("auto " ~ parameterNames_%2$s[i] ~ " = to!(" ~ (parameterTypes_%2$s[i]) ~ ")(request.getQuery(\"" ~ parameterNames_%2$s[i] ~ "\"));"); 137 } 138 else static if (isForm) 139 { 140 mixin("auto " ~ parameterNames_%2$s[i] ~ " = to!(" ~ (parameterTypes_%2$s[i]) ~ ")(request.getForm(\"" ~ parameterNames_%2$s[i] ~ "\"));"); 141 } 142 else static if (isPath) 143 { 144 mixin("auto " ~ parameterNames_%2$s[i] ~ " = to!(" ~ (parameterTypes_%2$s[i]) ~ ")(request.path[" ~ to!string(i + 2) ~ "]);"); 145 } 146 } 147 148 mixin("return controller.%2$s(" ~ (parameterNames_%2$s.join(",")) ~ ");"); 149 })); 150 } 151 else 152 { 153 static assert(0, "Unable to determine a mapping path for the action."); 154 } 155 } 156 else 157 { 158 mapRoutedAction(memberHttpMethod, memberActionName, 159 new ControllerAction(&controller.%2$s)); 160 } 161 } 162 }; 163 164 public class WebController(TView) : Controller 165 { 166 private: 167 TView _view; 168 169 public: 170 this(this TController)(TView view, IHttpRequest request, IHttpResponse response) 171 { 172 super(request, response); 173 174 _view = view; 175 176 mixin("import yurai.prebuild.views : " ~ TController.stringof.split("!")[1][1 .. $-1] ~ ";"); 177 178 import controllers; 179 import models; 180 181 auto controller = cast(TController)this; 182 183 foreach (member; __traits(derivedMembers, TController)) 184 {{ 185 static if (member != "__ctor") 186 { 187 mixin(memberNameFormat.format(TController.stringof, member)); 188 mixin(memberHttpMethodFormat.format(TController.stringof, member)); 189 190 mixin(defaultMappingFormat.format(TController.stringof, member)); 191 mixin(mandatoryMappingFormat.format(TController.stringof, member)); 192 mixin(actionMappingFormat.format(TController.stringof, member)); 193 } 194 }} 195 } 196 197 @property 198 { 199 TView view() { return _view; } 200 } 201 } 202 203 public class ApiController : Controller 204 { 205 public: 206 this(this TController)(IHttpRequest request, IHttpResponse response) 207 { 208 super(request, response); 209 210 import controllers; 211 import models; 212 213 auto controller = cast(TController)this; 214 215 foreach (member; __traits(derivedMembers, TController)) 216 {{ 217 static if (member != "__ctor") 218 { 219 mixin(memberNameFormat.format(TController.stringof, member)); 220 mixin(memberHttpMethodFormat.format(TController.stringof, member)); 221 222 mixin(defaultMappingFormat.format(TController.stringof, member)); 223 mixin(mandatoryMappingFormat.format(TController.stringof, member)); 224 mixin(actionMappingFormat.format(TController.stringof, member)); 225 } 226 }} 227 } 228 }