UNPKG

routing-controllers

Version:

Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage for Express / Koa using TypeScript.

229 lines 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ActionParameterHandler = void 0; const class_transformer_1 = require("class-transformer"); const class_validator_1 = require("class-validator"); const BadRequestError_1 = require("./http-error/BadRequestError"); const ParameterParseJsonError_1 = require("./error/ParameterParseJsonError"); const ParamRequiredError_1 = require("./error/ParamRequiredError"); const AuthorizationRequiredError_1 = require("./error/AuthorizationRequiredError"); const CurrentUserCheckerNotDefinedError_1 = require("./error/CurrentUserCheckerNotDefinedError"); const isPromiseLike_1 = require("./util/isPromiseLike"); const ParamNormalizationError_1 = require("./error/ParamNormalizationError"); /** * Handles action parameter. */ class ActionParameterHandler { // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- constructor(driver) { this.driver = driver; } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Handles action parameter. */ handle(action, param) { if (param.type === 'request') return action.request; if (param.type === 'response') return action.response; if (param.type === 'context') return action.context; // get parameter value from request and normalize it const value = this.normalizeParamValue(this.driver.getParamFromRequest(action, param), param); if ((0, isPromiseLike_1.isPromiseLike)(value)) return value.then(value => this.handleValue(value, action, param)); return this.handleValue(value, action, param); } // ------------------------------------------------------------------------- // Protected Methods // ------------------------------------------------------------------------- /** * Handles non-promise value. */ handleValue(value, action, param) { // if transform function is given for this param then apply it if (param.transform) value = param.transform(action, value); // if its current-user decorator then get its value if (param.type === 'current-user') { if (!this.driver.currentUserChecker) throw new CurrentUserCheckerNotDefinedError_1.CurrentUserCheckerNotDefinedError(); value = this.driver.currentUserChecker(action); } // check cases when parameter is required but its empty and throw errors in this case if (param.required) { const isValueEmpty = value === null || value === undefined || value === ''; const isValueEmptyObject = typeof value === 'object' && value !== null && Object.keys(value).length === 0; if (param.type === 'body' && !param.name && (isValueEmpty || isValueEmptyObject)) { // body has a special check and error message return Promise.reject(new ParamRequiredError_1.ParamRequiredError(action, param)); } else if (param.type === 'current-user') { // current user has a special check as well if ((0, isPromiseLike_1.isPromiseLike)(value)) { return value.then(currentUser => { if (!currentUser) return Promise.reject(new AuthorizationRequiredError_1.AuthorizationRequiredError(action)); return currentUser; }); } else { if (!value) return Promise.reject(new AuthorizationRequiredError_1.AuthorizationRequiredError(action)); } } else if (param.name && isValueEmpty) { // regular check for all other parameters // todo: figure out something with param.name usage and multiple things params (query params, upload files etc.) return Promise.reject(new ParamRequiredError_1.ParamRequiredError(action, param)); } } return value; } /** * Normalizes parameter value. */ async normalizeParamValue(value, param) { if (value === null || value === undefined) return value; const isNormalizationNeeded = typeof value === 'object' && ['queries', 'headers', 'params', 'cookies'].includes(param.type); const isTargetPrimitive = ['number', 'string', 'boolean'].includes(param.targetName); const isTransformationNeeded = (param.parse || param.isTargetObject) && param.type !== 'param'; // if param value is an object and param type match, normalize its string properties if (isNormalizationNeeded) { await Promise.all(Object.keys(value).map(async (key) => { const keyValue = value[key]; if (typeof keyValue === 'string') { const ParamType = Reflect.getMetadata('design:type', param.targetType.prototype, key); if (ParamType) { const typeString = ParamType.name.toLowerCase(); value[key] = await this.normalizeParamValue(keyValue, { ...param, name: key, targetType: ParamType, targetName: typeString, }); } } })); } // if value is a string, normalize it to demanded type else if (typeof value === 'string') { switch (param.targetName) { case 'number': case 'string': case 'boolean': case 'date': const normalizedValue = this.normalizeStringValue(value, param.name, param.targetName); return param.isArray ? [normalizedValue] : normalizedValue; case 'array': return [value]; } } else if (Array.isArray(value)) { return value.map(v => this.normalizeStringValue(v, param.name, param.targetName)); } // if target type is not primitive, transform and validate it if (!isTargetPrimitive && isTransformationNeeded) { value = this.parseValue(value, param); value = this.transformValue(value, param); value = await this.validateValue(value, param); } return value; } /** * Normalizes string value to number or boolean. */ normalizeStringValue(value, parameterName, parameterType) { switch (parameterType) { case 'number': if (value === '') { throw new ParamNormalizationError_1.InvalidParamError(value, parameterName, parameterType); } const valueNumber = +value; if (Number.isNaN(valueNumber)) { throw new ParamNormalizationError_1.InvalidParamError(value, parameterName, parameterType); } return valueNumber; case 'boolean': if (value === 'true' || value === '1' || value === '') { return true; } else if (value === 'false' || value === '0') { return false; } else { throw new ParamNormalizationError_1.InvalidParamError(value, parameterName, parameterType); } case 'date': const parsedDate = new Date(value); if (Number.isNaN(parsedDate.getTime())) { throw new ParamNormalizationError_1.InvalidParamError(value, parameterName, parameterType); } return parsedDate; case 'string': default: return value; } } /** * Parses string value into a JSON object. */ parseValue(value, paramMetadata) { if (typeof value === 'string') { if (['queries', 'query'].includes(paramMetadata.type) && paramMetadata.targetName === 'array') { return [value]; } else { try { return JSON.parse(value); } catch (error) { throw new ParameterParseJsonError_1.ParameterParseJsonError(paramMetadata.name, value); } } } return value; } /** * Perform class-transformation if enabled. */ transformValue(value, paramMetadata) { if (this.driver.useClassTransformer && paramMetadata.actionMetadata.options.transformRequest !== false && paramMetadata.targetType && paramMetadata.targetType !== Object && !(value instanceof paramMetadata.targetType)) { const options = paramMetadata.classTransform || this.driver.plainToClassTransformOptions; value = (0, class_transformer_1.plainToInstance)(paramMetadata.targetType, value, options); } return value; } /** * Perform class-validation if enabled. */ validateValue(value, paramMetadata) { const isValidationEnabled = paramMetadata.validate instanceof Object || paramMetadata.validate === true || (this.driver.enableValidation === true && paramMetadata.validate !== false); const shouldValidate = paramMetadata.targetType && paramMetadata.targetType !== Object && value instanceof paramMetadata.targetType; if (isValidationEnabled && shouldValidate) { const options = Object.assign({ forbidUnknownValues: false }, this.driver.validationOptions, paramMetadata.validate); return (0, class_validator_1.validateOrReject)(value, options) .then(() => value) .catch((validationErrors) => { const error = new BadRequestError_1.BadRequestError(`Invalid ${paramMetadata.type}, check 'errors' property for more info.`); error.errors = validationErrors; error.paramName = paramMetadata.name; throw error; }); } return value; } } exports.ActionParameterHandler = ActionParameterHandler; //# sourceMappingURL=ActionParameterHandler.js.map