UNPKG

nestjs-joi

Version:

Easy to use JoiPipe as an interface between joi and NestJS with optional decorator-based schema construction.

178 lines (177 loc) 7.61 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var JoiPipe_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.JoiPipe = void 0; const common_1 = require("@nestjs/common"); const core_1 = require("@nestjs/core"); const Joi = require("joi"); const joi_class_decorators_1 = require("joi-class-decorators"); const defs_1 = require("./defs"); const DEFAULT_JOI_OPTS = { abortEarly: false, allowUnknown: true, }; const DEFAULT_JOI_PIPE_OPTS = { group: undefined, usePipeValidationException: false, defaultValidationOptions: DEFAULT_JOI_OPTS, skipErrorFormatting: false, }; const JOI_PIPE_OPTS_KEYS = Object.keys(DEFAULT_JOI_PIPE_OPTS); function isHttpRequest(req) { return req && 'method' in req; } function isGraphQlRequest(req) { return req && 'req' in req && typeof req.req === 'object' && 'method' in req.req; } function isMicroserviceRequest(req) { return (req && 'pattern' in req && 'data' in req && 'context' in req && req.context instanceof Object); } let JoiPipe = JoiPipe_1 = class JoiPipe { constructor(arg, pipeOpts) { this.arg = arg; if (arg) { if (isHttpRequest(arg)) { this.method = arg.method.toUpperCase(); } else if (isGraphQlRequest(arg)) { } else if (isMicroserviceRequest(arg)) { } else { if (Joi.isSchema(arg)) { this.schema = arg; } else if (typeof arg === 'function') { this.type = arg; } else { pipeOpts = arg; } } } else { } this.pipeOpts = this.parseOptions(pipeOpts); } transform(payload, metadata) { const schema = this.getSchema(metadata); if (!schema) { return payload; } return JoiPipe_1.validate(payload, schema, this.pipeOpts.usePipeValidationException, this.pipeOpts.skipErrorFormatting, this.pipeOpts.defaultValidationOptions || DEFAULT_JOI_OPTS, metadata); } static validate(payload, schema, usePipeValidationException, skipErrorFormatting, validationOptions, metadata = { type: 'custom' }) { const { error, value } = schema.validate(payload, validationOptions); if (error) { if (Joi.isError(error)) { const reasons = error.details .map((detail) => detail.message) .join(', '); const formattedMessage = `Request validation of ${metadata.type} ` + (metadata.data ? `item '${metadata.data}' ` : '') + `failed, because: ${reasons}`; if (usePipeValidationException) { throw new defs_1.JoiPipeValidationException(skipErrorFormatting ? error.message : formattedMessage, error); } else { throw new common_1.BadRequestException(skipErrorFormatting ? error : formattedMessage); } } else { throw error; } } return value; } parseOptions(pipeOpts) { pipeOpts = Object.assign(Object.assign({}, DEFAULT_JOI_PIPE_OPTS), pipeOpts || {}); pipeOpts.defaultValidationOptions = Object.assign(Object.assign({}, DEFAULT_JOI_OPTS), pipeOpts.defaultValidationOptions || {}); const errors = []; const unknownKeys = Object.keys(pipeOpts).filter(k => !JOI_PIPE_OPTS_KEYS.includes(k)); if (unknownKeys.length) { errors.push(`Unknown configuration keys: ${unknownKeys.join(', ')}`); } if (pipeOpts.group && !(typeof pipeOpts.group === 'string' || typeof pipeOpts.group === 'symbol')) { errors.push(`'group' must be a string or symbol`); } if (Object.prototype.hasOwnProperty.call(pipeOpts, 'usePipeValidationException') && !(typeof pipeOpts.usePipeValidationException === 'boolean')) { errors.push(`'usePipeValidationException' must be a boolean`); } if (Object.prototype.hasOwnProperty.call(pipeOpts, 'skipErrorFormatting') && !(typeof pipeOpts.skipErrorFormatting === 'boolean')) { errors.push(`'skipErrorFormatting' must be a boolean`); } if (errors.length) { throw new Error(`Invalid JoiPipeOptions:\n${errors.map(x => `- ${x}`).join('\n')}`); } return pipeOpts; } getSchema(metadata) { if (this.method && metadata.metatype) { let group; if (this.method === 'PUT' || this.method === 'PATCH') { group = defs_1.JoiValidationGroups.UPDATE; } else if (this.method === 'POST') { group = defs_1.JoiValidationGroups.CREATE; } return JoiPipe_1.getTypeSchema(metadata.metatype, { group }); } if (this.schema) { return this.schema; } if (this.type) { return JoiPipe_1.getTypeSchema(this.type, { forced: true, group: this.pipeOpts.group }); } if (metadata.metatype) { return JoiPipe_1.getTypeSchema(metadata.metatype, { group: this.pipeOpts.group }); } return undefined; } static getTypeSchema(type, { forced, group } = {}) { if (type === String || type === Object || type === Number || type === Array) { return; } const cacheKey = 'forced' + (forced ? '1' : '0') + (group ? 'group' + String(group) : ''); if (this.typeSchemaMap.has(type)) { if (this.typeSchemaMap.get(type).has(cacheKey)) { return this.typeSchemaMap.get(type).get(cacheKey); } } else { this.typeSchemaMap.set(type, new Map()); } const typeSchema = (0, joi_class_decorators_1.getTypeSchema)(type, { group }); if ((!typeSchema || !Object.keys(typeSchema.describe().keys).length) && !forced) { this.typeSchemaMap.get(type).set(cacheKey, undefined); return undefined; } const finalSchema = typeSchema.required(); this.typeSchemaMap.get(type).set(cacheKey, finalSchema); return finalSchema; } }; JoiPipe.typeSchemaMap = new Map(); JoiPipe = JoiPipe_1 = __decorate([ (0, common_1.Injectable)({ scope: common_1.Scope.REQUEST }), __param(0, (0, common_1.Inject)(core_1.REQUEST)), __param(1, (0, common_1.Optional)()), __param(1, (0, common_1.Inject)(defs_1.JOIPIPE_OPTIONS)), __metadata("design:paramtypes", [Object, Object]) ], JoiPipe); exports.JoiPipe = JoiPipe;