UNPKG

18.8 kBJavaScriptView Raw
1/**
2 * @license
3 * MOST Web Framework 2.0 Codename Blueshift
4 * Copyright (c) 2017, THEMOST LP All rights reserved
5 *
6 * Use of this source code is governed by an BSD-3-Clause license that can be
7 * found in the LICENSE file at https://themost.io/license
8 */
9///
10var TraceUtils = require('@themost/common/utils').TraceUtils;
11var LangUtils = require('@themost/common/utils').LangUtils;
12var HttpBadRequestError = require('@themost/common/errors').HttpBadRequestError;
13var HttpUnauthorizedError = require('@themost/common/errors').HttpUnauthorizedError;
14var HttpConsumer = require('./consumers').HttpConsumer;
15var DataTypeValidator = require('@themost/data/data-validator').DataTypeValidator;
16var MinLengthValidator = require('@themost/data/data-validator').MinLengthValidator;
17var MaxLengthValidator = require('@themost/data/data-validator').MaxLengthValidator;
18var MinValueValidator = require('@themost/data/data-validator').MinValueValidator;
19var MaxValueValidator = require('@themost/data/data-validator').MaxValueValidator;
20var RequiredValidator = require('@themost/data/data-validator').RequiredValidator;
21var PatternValidator = require('@themost/data/data-validator').PatternValidator;
22
23/**
24 * @class
25 * @constructor
26 * @extends Error
27 * @augments Error
28 */
29function DecoratorError() {
30 DecoratorError.super_.call(this, 'Decorator is not valid on this declaration type.');
31}
32LangUtils.inherits(DecoratorError, Error);
33
34/**
35 * @param {string} name
36 * @returns {Function}
37 */
38function httpController(name) {
39 return function (target, key, descriptor) {
40 if (typeof target === 'function') {
41 target.httpController = true;
42 }
43 // define controller name
44 Object.defineProperty(target, 'httpControllerName', {
45 value: name,
46 configurable: false,
47 enumerable: true,
48 writable: true
49 });
50 return descriptor;
51 }
52}
53
54function httpGet() {
55 return function (target, key, descriptor) {
56 if (typeof descriptor.value === 'function') {
57 descriptor.value.httpGet = true;
58 }
59 return descriptor;
60 }
61}
62/**
63 * @returns {Function}
64 */
65function httpAny() {
66 return function (target, key, descriptor) {
67 if (typeof descriptor.value === 'function') {
68 descriptor.value.httpGet = true;
69 descriptor.value.httpPost = true;
70 descriptor.value.httpPut = true;
71 descriptor.value.httpDelete = true;
72 descriptor.value.httpOptions = true;
73 descriptor.value.httpHead = true;
74 }
75 return descriptor;
76 }
77}
78/**
79 * @returns {Function}
80 */
81function httpPost() {
82 return function (target, key, descriptor) {
83 if (typeof descriptor.value === 'function') {
84 descriptor.value.httpPost = true;
85 }
86 return descriptor;
87 }
88}
89/**
90 * @returns {Function}
91 */
92function httpPatch() {
93 return function (target, key, descriptor) {
94 if (typeof descriptor.value === 'function') {
95 descriptor.value.httpPatch = true;
96 }
97 return descriptor;
98 }
99}
100/**
101 * @returns {Function}
102 */
103function httpPut() {
104 return function (target, key, descriptor) {
105 if (typeof descriptor.value === 'function') {
106 descriptor.value.httpPut = true;
107 }
108 return descriptor;
109 }
110}
111/**
112 * @returns {Function}
113 */
114function httpDelete() {
115 return function (target, key, descriptor) {
116 if (typeof descriptor.value === 'function') {
117 descriptor.value.httpDelete = true;
118 }
119 return descriptor;
120 }
121}
122/**
123 * @returns {Function}
124 */
125function httpOptions() {
126 return function (target, key, descriptor) {
127 if (typeof descriptor.value === 'function') {
128 descriptor.value.httpOptions = true;
129 }
130 return descriptor;
131 }
132}
133/**
134 * @returns {Function}
135 */
136function httpHead() {
137 return function (target, key, descriptor) {
138 if (typeof descriptor.value === 'function') {
139 descriptor.value.httpHead = true;
140 }
141 return descriptor;
142 }
143}
144/**
145 * @returns {Function}
146 */
147function httpAction(name) {
148 if (typeof name !== 'string') {
149 throw new TypeError('Action name must be a string');
150 }
151 return function (target, key, descriptor) {
152 if (typeof descriptor.value !== 'function') {
153 throw new Error('Decorator is not valid on this declaration type.');
154 }
155 descriptor.value.httpAction = name;
156 return descriptor;
157 }
158}
159/**
160 *
161 * @param {string} name
162 * @param {string} alias
163 * @returns {Function}
164 */
165function httpParamAlias(name, alias) {
166 if (typeof name !== 'string') {
167 throw new TypeError('Parameter name must be a string');
168 }
169 if (typeof alias !== 'string') {
170 throw new TypeError('Parameter alias must be a string');
171 }
172 return function (target, key, descriptor) {
173 if (typeof descriptor.value !== 'function') {
174 throw new Error('Decorator is not valid on this declaration type.');
175 }
176 descriptor.value.httpParamAlias = descriptor.value.httpParamAlias || { };
177 descriptor.value.httpParamAlias[name] = alias;
178 return descriptor;
179 }
180}
181/**
182 * @class
183 * @abstract
184 * @property {string} name
185 * @property {string} type
186 * @property {RegExp|string} pattern
187 * @property {date|number|*} minValue
188 * @property {date|number|*} maxValue
189 * @property {number} minLength
190 * @property {number} maxLength
191 * @property {boolean} required
192 * @property {string} message
193 * @constructor
194 */
195// eslint-disable-next-line no-unused-vars
196function HttpParamAttributeOptions() {
197
198}
199
200/**
201 * @param {HttpParamAttributeOptions|*=} options
202 * @returns {Function}
203 */
204function httpParam(options) {
205 if (typeof options !== 'object') { throw new TypeError('Parameter options must be an object'); }
206 if (typeof options.name !== 'string') { throw new TypeError('Parameter name must be a string'); }
207 return function (target, key, descriptor) {
208 if (typeof descriptor.value !== 'function') {
209 throw new Error('Decorator is not valid on this declaration type.');
210 }
211
212 descriptor.value.httpParams = descriptor.value.httpParams || { };
213 descriptor.value.httpParams[options.name] = Object.assign({"type":"Text"}, options);
214 if (typeof descriptor.value.httpParam === 'undefined') {
215 descriptor.value.httpParam = new HttpConsumer(function (context) {
216 var httpParamValidationFailedCallback = function httpParamValidationFailedCallback(context, httpParam, validationResult) {
217 TraceUtils.error(JSON.stringify(Object.assign(validationResult, {
218 "param":httpParam,
219 "request": {
220 "url":context.request.url,
221 "method":context.request.method
222 }
223 })));
224 return Promise.reject(new HttpBadRequestError('Bad request parameter', httpParam.message || validationResult.message));
225 };
226 var methodParams = LangUtils.getFunctionParams(descriptor.value);
227 var httpParams = descriptor.value.httpParams;
228 if (methodParams.length>0) {
229 var k = 0, httpParam, validator, validationResult, functionParam, contextParam;
230 while (k < methodParams.length) {
231 functionParam = methodParams[k];
232 if (typeof context.getParam === 'function') {
233 contextParam = context.getParam(functionParam);
234 }
235 else {
236 contextParam = context.params[functionParam];
237 }
238 if (httpParams) {
239 httpParam = httpParams[functionParam];
240 if (httpParam) {
241 if (typeof httpParam.type === 'string') {
242 //--validate type
243 validator = new DataTypeValidator(httpParam.type);
244 validator.setContext(context);
245 validationResult = validator.validateSync(contextParam);
246 if (validationResult) {
247 return httpParamValidationFailedCallback(context, httpParam, validationResult);
248 }
249 }
250 if (httpParam.pattern instanceof RegExp) {
251 //--validate pattern
252 validator = new PatternValidator(httpParam.pattern);
253 validator.setContext(context);
254 validationResult = validator.validateSync(contextParam);
255 if (validationResult) {
256 return httpParamValidationFailedCallback(context, httpParam, validationResult);
257 }
258 }
259 if (typeof httpParam.minLength === 'number') {
260 //--validate min length
261 validator = new MinLengthValidator(httpParam.minLength);
262 validator.setContext(context);
263 validationResult = validator.validateSync(contextParam);
264 if (validationResult) {
265 return httpParamValidationFailedCallback(context, httpParam, validationResult);
266 }
267 }
268 if (typeof httpParam.maxLength === 'number') {
269 //--validate max length
270 validator = new MaxLengthValidator(httpParam.maxLength);
271 validator.setContext(context);
272 validationResult = validator.validateSync(contextParam);
273 if (validationResult) {
274 return httpParamValidationFailedCallback(context, httpParam, validationResult);
275 }
276 }
277 if (typeof httpParam.minValue !== 'undefined') {
278 //--validate min value
279 validator = new MinValueValidator(httpParam.minValue);
280 validator.setContext(context);
281 validationResult = validator.validateSync(contextParam);
282 if (validationResult) {
283 return httpParamValidationFailedCallback(context, httpParam, validationResult);
284 }
285 }
286 if (typeof httpParam.maxValue !== 'undefined') {
287 //--validate max value
288 validator = new MaxValueValidator(httpParam.required);
289 validator.setContext(context);
290 validationResult = validator.validateSync(contextParam);
291 if (validationResult) {
292 return httpParamValidationFailedCallback(context, httpParam, validationResult);
293 }
294 }
295
296 if ((typeof httpParam.required !== 'undefined') && (httpParam.required === true)) {
297 //--validate required value
298 validator = new RequiredValidator();
299 validator.setContext(context);
300 validationResult = validator.validateSync(contextParam);
301 if (validationResult) {
302 return httpParamValidationFailedCallback(context, httpParam, validationResult);
303 }
304 }
305 }
306 }
307 k += 1;
308 }
309 }
310 return Promise.resolve();
311 });
312 }
313 return descriptor;
314 }
315}
316
317/**
318 * @param {boolean=} value
319 * @returns {Function}
320 */
321function httpAuthorize(value) {
322 return function (target, key, descriptor) {
323 if (typeof descriptor.value !== 'function') {
324 throw new Error('Decorator is not valid on this declaration type.');
325 }
326 var authorize = true;
327 if (typeof value === 'boolean') {
328 authorize = value;
329 }
330 if (authorize) {
331 descriptor.value.authorize = new HttpConsumer(function (context) {
332 if (context.user && context.user.name !== 'anonymous') {
333 return Promise.resolve();
334 }
335 return Promise.reject(new HttpUnauthorizedError());
336 });
337 }
338 return descriptor;
339 };
340}
341
342/**
343 *
344 * @param {Object|Function} proto - The constructor function of a class or the prototype of a class
345 * @param {string} key - The name of the property or method where the decorator will be included
346 * @param {Function} decorator - The decorator to be included
347 */
348function defineDecorator(proto, key, decorator) {
349 if ((typeof proto !== 'object') && (typeof proto !== 'function')) {
350 throw new DecoratorError('Invalid prototype. Expected object or function.');
351 }
352 if (typeof key !== 'string') {
353 throw new DecoratorError('Invalid property name. Expected string.');
354 }
355 if (typeof decorator !== 'function') {
356 throw new DecoratorError('Invalid decorator. Expected function.');
357 }
358 decorator(proto, key, Object.getOwnPropertyDescriptor(proto, key));
359}
360
361/**
362 * @param {string} name
363 * @param {Function|HttpConsumer} consumer
364 * @returns {Function}
365 */
366function httpActionConsumer(name, consumer) {
367 return function (target, key, descriptor) {
368 if (typeof descriptor.value !== 'function') {
369 throw new Error('Decorator is not valid on this declaration type.');
370 }
371 if (consumer instanceof HttpConsumer) {
372 //set consumer
373 descriptor.value[name] = consumer;
374 //and exit
375 return descriptor;
376 }
377 //validate consumer function
378 if (typeof consumer !== 'function') {
379 throw new Error('Consumer may be a function.');
380 }
381 descriptor.value[name] = new HttpConsumer(consumer);
382 return descriptor;
383 };
384}
385
386/**
387 * Defines an http route that is going to be registered by an http controller
388 * @param {string} url
389 * @param {string=} format
390 * @param {number=} index
391 * @returns {Function}
392 */
393function httpRoute(url, format, index) {
394 return function (target, key, descriptor) {
395 if (typeof descriptor.value === 'function') {
396 Object.defineProperty(descriptor.value, 'httpRoute', {
397 get: function () {
398 /**
399 * @type {HttpRouteConfiguration}
400 */
401 var route = {
402 url: url,
403 controller: target.httpControllerName || target.name,
404 action: descriptor.value.httpAction,
405 format: format
406 };
407 if (descriptor.value.hasOwnProperty('httpAny') === false) {
408 // set httpHead if does not exists
409 if (descriptor.value.hasOwnProperty('httpHead') === false) {
410 descriptor.value.httpHead = true;
411 }
412 // set httpOptions if does not exists
413 if (descriptor.value.hasOwnProperty('httpOptions') === false) {
414 descriptor.value.httpOptions = true;
415 }
416 // enumerate http methods and format allow attribute
417 var allowString = [
418 'httpGet',
419 'httpHead',
420 'httpOptions',
421 'httpPost',
422 'httpPut',
423 'httpDelete',
424 'httpPatch' ].filter( function(httpKey) {
425 return descriptor.value.hasOwnProperty(httpKey) && descriptor.value[httpKey];
426 }).map(function(httpKey) {
427 return httpKey.replace(/^http/,'').toUpperCase();
428 }).join(',');
429 // set allow attribute
430 Object.assign(route, {
431 allow: allowString
432 });
433 }
434 return route;
435 },
436 configurable: false,
437 enumerable: true
438 });
439 // set route index
440 Object.defineProperty(descriptor.value, 'httpRouteIndex', {
441 value: index || 0
442 });
443 }
444 return descriptor;
445 }
446}
447
448//extend object
449if (typeof Object.defineDecorator === 'undefined') {
450 /**
451 * @function defineDecorator
452 * @param {Object|Function} proto - The constructor function of a class or the prototype of a class
453 * @param {string} key - The name of the property or method where the decorator will be included
454 * @param {Function} decorator - The decorator to be included
455 * @memberOf Object
456 * @static
457 */
458 Object.defineDecorator = defineDecorator;
459}
460
461module.exports.DecoratorError = DecoratorError;
462module.exports.httpGet = httpGet;
463module.exports.httpAny = httpAny;
464module.exports.httpPost = httpPost;
465module.exports.httpPut = httpPut;
466module.exports.httpPatch = httpPatch;
467module.exports.httpDelete = httpDelete;
468module.exports.httpOptions = httpOptions;
469module.exports.httpHead = httpHead;
470module.exports.httpAction = httpAction;
471module.exports.httpRoute = httpRoute;
472module.exports.httpController = httpController;
473module.exports.httpParamAlias = httpParamAlias;
474module.exports.httpParam = httpParam;
475module.exports.httpAuthorize = httpAuthorize;
476module.exports.defineDecorator = defineDecorator;
477module.exports.httpActionConsumer = httpActionConsumer;