1 module scorpion.controller; 2 3 import lighttp.util : Status, ServerRequest, ServerResponse; 4 5 import scorpion.context : Context; 6 7 /** 8 * Attribute for controllers to be used with classes that contain 9 * routes. 10 * Example: 11 * --- 12 * @Controller 13 * @Controller("path") 14 * @Controller("more", "complex", "path") 15 * --- 16 */ 17 struct Controller { 18 19 string[] path; 20 21 this(string[] path...) { 22 this.path = path; 23 } 24 25 } 26 27 /** 28 * Attributes for routes. 29 * Indicates the method used (case sensitive, usually uppercase), 30 * whether the method can have a body and the path. 31 * Example: 32 * --- 33 * @Route("GET", false, "hello", "world") // GET /hello/world 34 * @Post // POST / 35 * @Delete("resource", "([a-z]+)") // DELETE /resource/:name 36 * --- 37 */ 38 struct Route { 39 40 /** 41 * Method accepted, conventionally uppercase. 42 */ 43 string method; 44 45 /** 46 * Indicates whether the request can have a body. If set false 47 * the body, if present, is always ignored. 48 */ 49 bool hasBody; 50 51 /** 52 * Route's path. It is later glued used path separators by 53 * the router manager. 54 */ 55 string[] path; 56 57 this(string method, bool hasBody, string[] path...) { 58 this.method = method; 59 this.hasBody = hasBody; 60 this.path = path; 61 } 62 63 } 64 65 /// ditto 66 Route Get(string[] path...) { 67 return Route("GET", false, path); 68 } 69 70 /// ditto 71 Route Post(string[] path...) { 72 return Route("POST", true, path); 73 } 74 75 /// ditto 76 Route Put(string[] path...) { 77 return Route("PUT", true, path); 78 } 79 80 /// ditto 81 Route Patch(string[] path...) { 82 return Route("PATCH", true, path); 83 } 84 85 /// ditto 86 Route Delete(string[] path...) { 87 return Route("DELETE", true, path); 88 } 89 90 /** 91 * Callable functions are routes that can be called from javascipt 92 * using scorpion's javscript file (served to `/assets/scorpion.js`), 93 * calling the `scorpion.call` javscript function. 94 * The javascipt function takes a arguments the name of the function, 95 * an object with the function's parameter and a callback. Both the 96 * object and the callbacks are optional. 97 * Example: 98 * --- 99 * // D code 100 * @Callable 101 * uint randomInteger() { 102 * return uniform!uint(); 103 * } 104 * 105 * // JS code 106 * scorpion.call("randomInteger", {}, number => console.log(number)); 107 * --- 108 * Example: 109 * --- 110 * // D code 111 * @Callable 112 * void startProcess(string name) { 113 * processFactory.start(name); 114 * } 115 * 116 * // JS code 117 * scorpion.call("startProcess", {name: "my_process"}); 118 * --- 119 * The `Callable` attribute, like other routes, can also be used with 120 * other custom attributes such as `Auth` and `AuthRedirect`. 121 */ 122 struct Callable { 123 124 /** 125 * Optional name of the function. If not present it defaults 126 * to the function's name the `Callable` attribute is associated 127 * with. 128 */ 129 string functionName; 130 131 } 132 133 /** 134 * Useful regular expressions for routing. 135 * Note that every regular expression in this enum is enclosed 136 * in a capturing group. 137 */ 138 enum Paths : string { 139 140 /** 141 * Matches a number between 0 and 255. 142 */ 143 signedByte = `(1?[0-9]{1,2}|2[0-4][0-9]|25[0-5])`, 144 145 /** 146 * Matches a number between 0 and 999,999,999. 147 */ 148 integer = `([0-9]{1,9})`, 149 150 /** 151 * Matches a number between 1 and 31. 152 */ 153 day = `(3[01]|[12][0-9]|[1-9])`, 154 155 /** 156 * Matches a number between 1 and 12. 157 */ 158 month = `([1-9]|1[0-2])`, 159 160 } 161 162 /** 163 * Attribute that indicates that the paramater is from a path's 164 * regex capture. 165 * The number of parameters annotated with `@Path` should correspond 166 * to the number of captures in the path. 167 * The type of the parameter can be any type that can be converted 168 * from a string: it's the programmer's duty to write a regular expression 169 * that won't cause any conversion exception. 170 * Example: 171 * --- 172 * @Get("([a-z]+)") 173 * _(Response response, @Path string capture) { 174 * ... 175 * } 176 * --- 177 */ 178 enum Path; 179 180 /** 181 * Attribute that indicates that the parameter is from a path's 182 * query. 183 * The parameter's type can be any type that can be converted from 184 * a string. If the conversion fails a `bad request` client error 185 * is returned to the client and the handler is not called. 186 * Example: 187 * --- 188 * @Get("hello") 189 * _(Response response, @Param string username) { 190 * response.body_ = "Your username: " ~ username; 191 * } 192 * --- 193 */ 194 struct Param { 195 196 /** 197 * Indicates the name of the parameter. If not present defaults 198 * to the identifier of the function's parameter that the `Param` 199 * attribute is associated to. 200 */ 201 string param; 202 203 } 204 205 /** 206 * Attribute that indicates that the parameter is converted from 207 * the request's body. 208 * The parameter must be either a struct or a class that will be 209 * insantiated by the validator and validated by it. 210 * The method is not usually called when the validation fails, but 211 * if the method also contains a `scorpion.validation.Validation` 212 * object as paramter the method is called even when the object 213 * was not successfully validated. 214 * To learn more about the validation process and the attributes 215 * that can be added to the object's members to validate see the 216 * `scorpion.validation` module's documentation. 217 * Example: 218 * --- 219 * struct Message { 220 * 221 * string sender, message; 222 * 223 * } 224 * 225 * @Post("hello") 226 * _(Response response, @Body Message message) { 227 * response.body_ = "Hello, " ~ message.sender ~ ", thanks for the message."; 228 * } 229 * --- 230 */ 231 enum Body; 232 233 /** 234 * Attribute that marks a function as asynchronous. It means that the 235 * response is not sent to the client when the method returns but when 236 * the `send` function is called in the response. 237 * This attribute should be used when the route's method permorms an 238 * asynchrous action such as a client http call or using the database. 239 * Example: 240 * --- 241 * @Async 242 * @Get 243 * getAsync(Response response) { 244 * new Client().connect("example.com").get("/").success((ClientResponse cresponse){ 245 * response.body = cresponse.body; 246 * response.send(); 247 * }); 248 * } 249 * --- 250 */ 251 struct Async { 252 253 bool test(Context context) { 254 context.response.ready = false; 255 return true; 256 } 257 258 }