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 }