1 /**
2  * Модуль контроллера генерирующий обработчики в compile time на основе интерфейсов
3  *
4  * Copyright: (c) 2015-2020, Milofon Project.
5  * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file.
6  * Author: <m.galanin@milofon.pro> Maksim Galanin
7  * Date: 2020-08-01
8  */
9 
10 module dango.web.controllers.generic;
11 
12 public
13 {
14     import dango.web.controller;
15 }
16 
17 private
18 {
19     import std.functional : toDelegate;
20     import std.traits;
21     import std.meta;
22 
23     import bolts : FilterMembersOf, protectionLevel, ProtectionLevel;
24 
25     import dango.inject.provider : ClassProvider;
26     import dango.system.logging : logError;
27     import dango.web.server : joinInetPath;
28 }
29 
30 
31 /**
32  * Аннотация для обозначение объекта контроллера
33  */
34 struct ControllerAttribute(alias W)
35 {
36     string prefix;
37 }
38 
39 
40 /**
41  * Аннотация для обозначение объекта контроллера
42  * Params:
43  *
44  * prefix = Префикс для всех путей
45  */
46 ControllerAttribute!W Controller(alias W = defaultHandler)(string prefix = "")
47 {
48     return ControllerAttribute!W(prefix);
49 }
50 
51 
52 /**
53  * Аннотация для обозначения метода для обработки входящих запросов
54  * Params:
55  *
56  * path   = Путь
57  * method = Метод
58  */
59 struct Handler(HTTPMethod M = HTTPMethod.GET)
60 {
61     string path;
62     enum method = M;
63 }
64 
65 alias Get = Handler!(HTTPMethod.GET);
66 alias Post = Handler!(HTTPMethod.POST);
67 alias Put = Handler!(HTTPMethod.PUT);
68 alias Delete = Handler!(HTTPMethod.DELETE);
69 
70 @("Should work Hander")
71 @safe unittest
72 {
73     auto hdl = Post("/");
74     assert (hdl.method == HTTPMethod.POST);
75 
76     @Post("/")
77     void handler() {}
78     assert (hasUDA!(handler, Handler));
79 
80     void noHandler() {}
81     assert (!hasUDA!(noHandler, Handler));
82 
83     @Handler!(HTTPMethod.HEAD)("/h")
84     void customHandler() {}
85 
86     enum udas = getUDAs!(customHandler, Handler);
87     assert (udas.length);
88     assert (udas[0].method == HTTPMethod.HEAD);
89     assert (udas[0].path == "/h");
90 }
91 
92 
93 /**
94  * Обертка над обработчиками по умочлчанию
95  */
96 HTTPServerRequestDelegate defaultHandler(C, HType, alias M)(C controller, HType hdl) @safe
97 {
98     return hdl;
99 }
100 
101 
102 /**
103  * Базовый класс web контроллера
104  * Params:
105  * CType = Объект с определенными в нем обработчиками
106  */
107 class GenericWebController(CType) : WebController
108     if (is(CType == class))
109 {
110     private
111     {
112         alias CTLs = GetControllerUDAs!CType;
113         static assert(CTLs.length,
114                 "Class '" ~ CType.stringof ~ "' is not controller");
115         enum CTL = CTLs[0];
116         alias WRAP = TemplateArgsOf!(typeof(CTL))[0];
117         enum __errorMsg = "The handler creation function must match '" ~
118             WRAP.stringof ~ "'";
119         CType _controller;
120     }
121 
122     /**
123      * Main constructor
124      */
125     this(CType controller) @safe
126     {
127         this._controller = controller;
128     }
129 
130     /**
131      * Регистрация цепочек маршрутов контроллера
132      */
133     void registerChains(RegisterChainCallback dg) @safe
134     {
135         alias Handlers = GetWebControllerHandlers!CType;
136         static assert(Handlers.length, "The controller '" ~ CType.stringof
137                 ~ "' must contain handlers");
138 
139         foreach(MemberName; Handlers)
140         {
141             alias Member = Alias!(__traits(getMember, CType, MemberName));
142             enum hdlUDA = getUDAs!(Member, Handler)[0];
143             auto HDL = &__traits(getMember, _controller, MemberName);
144             alias MemberType = typeof(toDelegate(HDL));
145             enum ident = __traits(identifier, Member);
146 
147             alias __wrap = WRAP!(CType, MemberType, Member);
148             static assert(is(ReturnType!__wrap == HTTPServerRequestDelegate),
149                     "Handler must return HTTPServerRequestDelegate");
150             alias __P = Parameters!__wrap;
151             static assert(__P.length == 2, __errorMsg);
152             static assert(is(__P[0] : CType), __errorMsg);
153             static assert(is(__P[1] == MemberType), __errorMsg);
154 
155             auto hdl = __wrap(_controller, HDL);
156             if (hdl !is null)
157                 dg(hdlUDA.method, getFullPath(hdlUDA.path), new Chain(hdl));
158             else
159                 logError("Handler '%s' in controller '%s' not register",
160                         MemberName, CType.stringof);
161         }
162     }
163 
164 
165     private string getFullPath(string path)
166     {
167         static if (CTL.prefix.length > 0)
168             return joinInetPath(CTL.prefix, path);
169         else
170             return path;
171     }
172 }
173 
174 
175 /**
176  * Фабрика для контроллера на основе кодогенерации
177  */
178 class GenericWebControllerFactory(CType) : WebControllerFactory
179     if (is(CType == class))
180 {
181     static assert(IsGenericController!CType,
182             "Class '" ~ CType.stringof ~ "' is not controller");
183 
184     /**
185      * Создает новый контроллер
186      */
187     WebController createComponent(DependencyContainer cnt, UniConf conf) @safe
188     {
189         CType controller;
190         auto provider = new ClassProvider!(CType, CType)(cnt);
191         provider.withProvided(true, (val) @trusted {
192                 controller = cast(CType)(*(cast(Object*)val));
193             });
194         return new GenericWebController!CType(controller);
195     }
196 }
197 
198 
199 private:
200 
201 
202 /**
203  * Возвращает список обработчиков контроллера
204  * Params:
205  * C = Проверяемый тип
206  */
207 template GetWebControllerHandlers(C)
208 {
209     template IsHandler(T, string name)
210     {
211         alias Member = Alias!(__traits(getMember, T, name));
212         static if (protectionLevel!Member == ProtectionLevel.public_)
213         {
214             static if (isCallable!Member && getUDAs!(Member, Handler).length)
215                 enum IsHandler = true;
216             else
217                 enum IsHandler = false;
218         }
219         else
220             enum IsHandler = false;
221     }
222 
223     alias GetWebControllerHandlers = FilterMembersOf!(C, IsHandler);
224 }
225 
226 
227 /**
228  * Проверка на тип контроллера
229  */
230 template IsGenericController(CType)
231 {
232     enum IsGenericController = GetControllerUDAs!CType.length > 0;
233 }
234 
235 
236 /**
237  * Возвращает аннотации контроллера
238  */
239 template GetControllerUDAs(CType)
240 {
241     template IsController(alias A)
242     {
243         static if (!isType!A)
244         {
245             enum V = A; // call is function
246             enum IsController = __traits(isSame,
247                     TemplateOf!(typeof(V)), ControllerAttribute);
248         }
249         else
250             enum IsController = false;
251     }
252 
253     alias GetControllerUDAs = Filter!(IsController,
254             __traits(getAttributes, CType));
255 }
256