1 /**
2  * Authors: Szabo Bogdan <szabobogdan@yahoo.com>
3  * Date: 9 13, 2015
4  * License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
5  * Copyright: Public Domain
6  */
7 module openapi.composites;
8 
9 import std.stdio, std.traits, std.exception, std.conv;
10 import vibe.http.server;
11 import vibe.http.router;
12 import openapi.definitions;
13 import openapi.exceptions;
14 
15 alias VibeHandler = void function(HTTPServerRequest, HTTPServerResponse);
16 
17 struct swaggerPath {
18   string path;
19   OperationsType type;
20 
21   @property string vibePath() {
22     import std.array : replace;
23     return path.replace("{", ":").replace("}", "");
24   }
25 }
26 
27 struct ErrorOutput {
28   string[] errors;
29 
30   this(Throwable e) {
31     errors ~= e.msg;
32   }
33 }
34 
35 template Alias(alias S)
36 {
37   alias Alias = S;
38 }
39 
40 private string alignString(string path, int max = 30) {
41   if(path.length < max) {
42     foreach(i; path.length..max) {
43       path ~= ' ';
44     }
45   }
46 
47   return path;
48 }
49 
50 VibeHandler[string][OperationsType] findComposites(BaseModule...)() {
51   import std.uni: toUpper;
52 
53   VibeHandler[string][OperationsType] list;
54 
55   static if(__traits(allMembers, BaseModule).length > 0) {
56     pragma(msg, "\nMap OpenApi Paths:");
57 
58     foreach(symbol_name; __traits(allMembers, BaseModule))
59     {
60       static if(symbol_name.length < 12 || symbol_name[3..12] != "TypeInfo_") {
61         static if(__traits(compiles, typeof(Alias!(__traits(getMember, BaseModule, symbol_name))))) {
62           alias symbol = Alias!(__traits(getMember, BaseModule, symbol_name));
63           static if(__traits(compiles, typeof(symbol)) && isSomeFunction!symbol) {
64             foreach(attr; __traits(getAttributes, symbol)) {
65               static if(attr.stringof.length > 12 && attr.stringof[0..12] == "swaggerPath(") {
66                 pragma(msg, alignString(attr.type, 8), alignString(attr.path), " => ", symbol_name);
67                 list[attr.vibePath][attr.type] = &symbol;
68               }
69             }
70           }
71         }
72       }
73     }
74     pragma(msg, "\n");
75   }
76 
77   return list;
78 }
79 
80 auto validation(VibeHandler handler, OpenApi definitions) {
81   import openapi.validation;
82 
83   void doValidation(HTTPServerRequest req, HTTPServerResponse res) {
84     writeln(req.method.to!string ~ " " ~ req.path);
85     try {
86       req.validate!(ParameterIn.path)(definitions);
87       req.validate!(ParameterIn.query)(definitions);
88       req.validate!(ParameterIn.header)(definitions);
89       req.validateBody(definitions);
90 
91       handler(req, res);
92     } catch(OpenApiValidationException e) {
93       res.writeJsonBody(ErrorOutput(e), HTTPStatus.badRequest);
94       debug {
95         writeln(e);
96       }
97     } catch(OpenApiParameterException e) {
98       res.writeJsonBody(ErrorOutput(e), HTTPStatus.badRequest);
99       debug {
100         writeln(e);
101       }
102     } catch(OpenApiNotFoundException e) {
103       res.writeJsonBody(ErrorOutput(e), HTTPStatus.notFound);
104       debug {
105         writeln(e);
106       }
107     } catch(Throwable e) {
108       res.writeJsonBody(ErrorOutput(e), HTTPStatus.internalServerError);
109 
110       debug {
111         writeln(e);
112         res.writeJsonBody(ErrorOutput(e));
113       } else {
114         res.writeBody("{ errors: [\"Internal server error\"] }");
115       }
116     }
117   }
118 
119   return &doValidation;
120 }
121 
122 void register(BaseModule...)(URLRouter router) {
123   enum auto handlers = findComposites!BaseModule;
124 
125   foreach(path, methods; handlers) {
126     with (router.route(path)) {
127       foreach(method, handler; methods) {
128         switch(method) {
129           case OperationsType.get:
130             get(handler);
131             break;
132           case OperationsType.put:
133             put(handler);
134             break;
135           case OperationsType.post:
136             post(handler);
137             break;
138           case OperationsType.delete_:
139             delete_(handler);
140             break;
141           case OperationsType.patch:
142             patch(handler);
143             break;
144           default:
145             enforce("method `" ~ method ~ "` not found");
146         }
147       }
148     }
149   }
150 }
151 
152 void register(BaseModule...)(URLRouter router, OpenApi definitions) {
153   const auto handlers = findComposites!BaseModule;
154 
155   auto basePath = definitions.basePath == "/" ? "" : definitions.basePath;
156 
157   foreach(path, methods; handlers) {
158     with (router.route(basePath ~ path)) {
159       foreach(method, handler; methods) {
160         switch(method) {
161           case OperationsType.get:
162             get(handler.validation(definitions));
163             break;
164           case OperationsType.put:
165             put(handler.validation(definitions));
166             break;
167           case OperationsType.post:
168             post(handler.validation(definitions));
169             break;
170           case OperationsType.delete_:
171             delete_(handler.validation(definitions));
172             break;
173           case OperationsType.patch:
174             patch(handler.validation(definitions));
175             break;
176           case OperationsType.options:
177             match(HTTPMethod.OPTIONS, handler.validation(definitions));
178             break;
179           default:
180             enforce("method `" ~ method ~ "` not found");
181         }
182       }
183     }
184   }
185 }