UNPKG

yasui

Version:

Lightweight Express-based framework for REST and web APIs

122 lines 13 kB
import { ReflectMetadata, getMetadata } from './reflect.js'; import { SwaggerService } from './swagger.service.js'; import { LoggerService } from '../services/index.js'; import { Scopes } from '../enums/index.js'; export class DecoratorValidator { appConfig; errors; constructor(appConfig) { this.appConfig = appConfig; this.errors = {}; } outputErrors() { if (!this.hasError()) { return; } let errorLog = '\n🚨 Decorator validation errors:'; for (const err of Object.values(this.errors).flat()) { errorLog += `\n • ${err.className}: ${err.issue}.`; if (err.suggestion) { errorLog += `\n 💡 ${err.suggestion}.`; } } new LoggerService().error(errorLog); } hasError() { return !!Object.keys(this.errors).length; } validateController(target) { if (typeof target !== 'function') { this.addError('App', 'Controller must be a class', 'Use @Controller on a class declaration'); } const routes = getMetadata(ReflectMetadata.ROUTES, target.prototype) || []; if (routes.length === 0) { this.addError(target.name, 'Controller has no route methods', 'Add @Get, @Post, @Put, @Delete, or @Patch methods'); } routes.forEach((route) => { this.validateRouteMethod(target.name, target.prototype, route); }); this.throwError(target.name); } validateInjectable(target, scope, buildStack) { const buildStackArray = Array.from(buildStack); const callerName = buildStackArray[buildStackArray.length - 1]; if (!this.isConstructible(target)) { this.addError(callerName, 'Injectable must be a class', 'Use @Injectable on a class declaration'); } const cycle = [...buildStackArray, target.name].join(' -> '); if (buildStackArray.indexOf(target.name) !== -1) { if (scope === Scopes.SHARED || target.name === callerName) { this.addError(callerName, `Circular dependency detected: ${cycle}`, 'Consider refactoring your dependencies or use @Scope(Scopes.LOCAL) for local instances'); } } const deps = getMetadata(ReflectMetadata.DESIGN_PARAM_TYPES, target) || []; const preInjectedDeps = getMetadata(ReflectMetadata.PRE_INJECTED_DEPS, target) || {}; deps.forEach((Dep, idx) => { if (Dep === undefined || Dep.prototype.toString() === 'function () { [native code] }') { this.addError(callerName, `Dependency at position ${idx} is undefined (${cycle} -> <?>)`, 'This usually means there is a circular import between files or a missing or bad import statement' + '\nCheck your import statements and ensure there is no circular import between files'); } else if (preInjectedDeps[idx]) { this.validateInjectionTokenRegistration(callerName, preInjectedDeps[idx]); } else if (!getMetadata(ReflectMetadata.INJECTABLE, Dep)) { this.addError(callerName, `Dependency at position ${idx} (${callerName} -> ${Dep.name}) is not injectable`, 'Add @Injectable on class declaration'); } }); this.throwError(callerName); } validateInjectionTokenRegistration(callerName, token) { if (!this.appConfig.injections?.find((inj) => inj.token === token)) { this.addError(callerName, `Injection token '${token}' is not registered`, `Register token in your app config: \`{ ..., injections: [..., { token: '${token}', provide: <any> }] }\``); } } validateSwaggerSchemaName(callerName, name) { const existingSchemaClass = SwaggerService.schemas.get(name)?.className; if (existingSchemaClass !== callerName) { this.addError(callerName, `Schema '${name}' already exists (Class ${existingSchemaClass})`, 'Use a different name for your schema'); } } validateRouteMethod(className, prototype, route) { if (!prototype[route.methodName]) { return; } const paramTypes = getMetadata(ReflectMetadata.DESIGN_PARAM_TYPES, prototype, route.methodName) || []; const paramNames = this.getParameterNames(prototype[route.methodName]); const methodsInjections = getMetadata(ReflectMetadata.METHOD_INJECTED_DEPS, prototype) || {}; paramTypes.forEach((type, index) => { const hasDecorator = !!(methodsInjections[route.methodName] || {})[index] || route.params.some(param => param.index === index); if (!hasDecorator) { this.addError(className, `Parameter '${paramNames[index]}' in ${route.methodName}() needs a decorator`, 'Add @Req, @Res, @Next, @Header, @Param, @Query, @Body, @Logger or @Inject decorator'); } }); } getParameterNames(func) { const funcStr = func.toString(); const match = funcStr.match(/\(([^)]*)\)/); if (!match || !match[1]) { return []; } ; return match[1] .split(',') .map(param => param.trim().split(/\s+/)[0] || '') .filter(name => name && name !== ''); } addError(className, issue, suggestion) { if (!this.errors[className]) { this.errors[className] = []; } this.errors[className].push({ className, issue, suggestion }); } throwError(className) { if (this.errors[className]?.length) { throw new Error(`${className} has not passed its validation checks`); } } isConstructible(fn) { return typeof fn === 'function' && !!fn.prototype && fn.prototype.constructor === fn; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVjb3JhdG9yLXZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9kZWNvcmF0b3ItdmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxlQUFlLEVBQUUsV0FBVyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQzVELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDckQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBaUIzQyxNQUFNLE9BQU8sa0JBQWtCO0lBR0E7SUFGckIsTUFBTSxDQUFvQztJQUVsRCxZQUE2QixTQUFzQjtRQUF0QixjQUFTLEdBQVQsU0FBUyxDQUFhO1FBQ2pELElBQUksQ0FBQyxNQUFNLEdBQUcsRUFBRSxDQUFDO0lBQ25CLENBQUM7SUFFTSxZQUFZO1FBQ2pCLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQztZQUNyQixPQUFPO1FBQ1QsQ0FBQztRQUNELElBQUksUUFBUSxHQUFXLG1DQUFtQyxDQUFDO1FBQzNELEtBQUssTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUNwRCxRQUFRLElBQUksU0FBUyxHQUFHLENBQUMsU0FBUyxLQUFLLEdBQUcsQ0FBQyxLQUFLLEdBQUcsQ0FBQztZQUNwRCxJQUFJLEdBQUcsQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFDbkIsUUFBUSxJQUFJLFlBQVksR0FBRyxDQUFDLFVBQVUsR0FBRyxDQUFDO1lBQzVDLENBQUM7UUFDSCxDQUFDO1FBQ0QsSUFBSSxhQUFhLEVBQUUsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDdEMsQ0FBQztJQUVNLFFBQVE7UUFDYixPQUFPLENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDM0MsQ0FBQztJQUVNLGtCQUFrQixDQUFDLE1BQWtDO1FBQzFELElBQUksT0FBTyxNQUFNLEtBQUssVUFBVSxFQUFFLENBQUM7WUFDakMsSUFBSSxDQUFDLFFBQVEsQ0FDWCxLQUFLLEVBQ0wsNEJBQTRCLEVBQzVCLHdDQUF3QyxDQUN6QyxDQUFDO1FBQ0osQ0FBQztRQUVELE1BQU0sTUFBTSxHQUFHLFdBQVcsQ0FBQyxlQUFlLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDM0UsSUFBSSxNQUFNLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ3hCLElBQUksQ0FBQyxRQUFRLENBQ1gsTUFBTSxDQUFDLElBQUksRUFDWCxpQ0FBaUMsRUFDakMsbURBQW1ELENBQ3BELENBQUM7UUFDSixDQUFDO1FBRUQsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLEtBQXVCLEVBQUUsRUFBRTtZQUN6QyxJQUFJLENBQUMsbUJBQW1CLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ2pFLENBQUMsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDL0IsQ0FBQztJQUlNLGtCQUFrQixDQUN2QixNQUFnQixFQUNoQixLQUFhLEVBQ2IsVUFBdUI7UUFFdkIsTUFBTSxlQUFlLEdBQWEsS0FBSyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUN6RCxNQUFNLFVBQVUsR0FBVyxlQUFlLENBQUMsZUFBZSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQztRQUV2RSxJQUFJLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1lBQ2xDLElBQUksQ0FBQyxRQUFRLENBQ1gsVUFBVSxFQUNWLDRCQUE0QixFQUM1Qix3Q0FBd0MsQ0FDekMsQ0FBQztRQUNKLENBQUM7UUFFRCxNQUFNLEtBQUssR0FBVyxDQUFDLEdBQUcsZUFBZSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFFckUsSUFBSSxlQUFlLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQ2hELElBQUksS0FBSyxLQUFLLE1BQU0sQ0FBQyxNQUFNLElBQUksTUFBTSxDQUFDLElBQUksS0FBSyxVQUFVLEVBQUUsQ0FBQztnQkFDMUQsSUFBSSxDQUFDLFFBQVEsQ0FDWCxVQUFVLEVBQ1YsaUNBQWlDLEtBQUssRUFBRSxFQUN4Qyx3RkFBd0YsQ0FDekYsQ0FBQztZQUNKLENBQUM7UUFDSCxDQUFDO1FBRUQsTUFBTSxJQUFJLEdBQUcsV0FBVyxDQUFDLGVBQWUsQ0FBQyxrQkFBa0IsRUFBRSxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDM0UsTUFBTSxlQUFlLEdBQUcsV0FBVyxDQUFDLGVBQWUsQ0FBQyxpQkFBaUIsRUFBRSxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7UUFFckYsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsRUFBRTtZQUN4QixJQUFJLEdBQUcsS0FBSyxTQUFTLElBQUksR0FBRyxDQUFDLFNBQVMsQ0FBQyxRQUFRLEVBQUUsS0FBSywrQkFBK0IsRUFBRSxDQUFDO2dCQUN0RixJQUFJLENBQUMsUUFBUSxDQUNYLFVBQVUsRUFDViwwQkFBMEIsR0FBRyxrQkFBa0IsS0FBSyxVQUFVLEVBQzlELGtHQUFrRztzQkFDaEcscUZBQXFGLENBQ3hGLENBQUM7WUFFSixDQUFDO2lCQUFNLElBQUksZUFBZSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQ2hDLElBQUksQ0FBQyxrQ0FBa0MsQ0FBQyxVQUFVLEVBQUUsZUFBZSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7WUFFNUUsQ0FBQztpQkFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLGVBQWUsQ0FBQyxVQUFVLEVBQUUsR0FBRyxDQUFDLEVBQUUsQ0FBQztnQkFDekQsSUFBSSxDQUFDLFFBQVEsQ0FDWCxVQUFVLEVBQ1YsMEJBQTBCLEdBQUcsS0FBSyxVQUFVLE9BQU8sR0FBRyxDQUFDLElBQUkscUJBQXFCLEVBQ2hGLHNDQUFzQyxDQUN2QyxDQUFDO1lBQ0osQ0FBQztRQUNILENBQUMsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFVBQVUsQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUM5QixDQUFDO0lBRU0sa0NBQWtDLENBQ3ZDLFVBQWtCLEVBQ2xCLEtBQWE7UUFFYixJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUMsR0FBdUIsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLEtBQUssS0FBSyxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ3ZGLElBQUksQ0FBQyxRQUFRLENBQ1gsVUFBVSxFQUNWLG9CQUFvQixLQUFLLHFCQUFxQixFQUM5QywyRUFBMkUsS0FBSywwQkFBMEIsQ0FDM0csQ0FBQztRQUNKLENBQUM7SUFDSCxDQUFDO0lBRU0seUJBQXlCLENBQzlCLFVBQWtCLEVBQ2xCLElBQVk7UUFFWixNQUFNLG1CQUFtQixHQUFHLGNBQWMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLFNBQVMsQ0FBQztRQUN4RSxJQUFJLG1CQUFtQixLQUFLLFVBQVUsRUFBRSxDQUFDO1lBQ3ZDLElBQUksQ0FBQyxRQUFRLENBQ1gsVUFBVSxFQUNWLFdBQVcsSUFBSSwyQkFBMkIsbUJBQW1CLEdBQUcsRUFDaEUsc0NBQXNDLENBQ3ZDLENBQUM7UUFDSixDQUFDO0lBQ0gsQ0FBQztJQUdPLG1CQUFtQixDQUN6QixTQUFpQixFQUNqQixTQUFtQyxFQUNuQyxLQUF1QjtRQUV2QixJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1lBQ2pDLE9BQU87UUFDVCxDQUFDO1FBQ0QsTUFBTSxVQUFVLEdBQUcsV0FBVyxDQUFDLGVBQWUsQ0FBQyxrQkFBa0IsRUFBRSxTQUFTLEVBQUUsS0FBSyxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN0RyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDO1FBRXZFLE1BQU0saUJBQWlCLEdBQUcsV0FBVyxDQUFDLGVBQWUsQ0FBQyxvQkFBb0IsRUFBRSxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUM7UUFHN0YsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQWMsRUFBRSxLQUFhLEVBQUUsRUFBRTtZQUNuRCxNQUFNLFlBQVksR0FBRyxDQUFDLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDO21CQUNwRSxLQUFLLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxLQUFLLEtBQUssS0FBSyxDQUFDLENBQUM7WUFDdkQsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO2dCQUNsQixJQUFJLENBQUMsUUFBUSxDQUNYLFNBQVMsRUFDVCxjQUFjLFVBQVUsQ0FBQyxLQUFLLENBQUMsUUFBUSxLQUFLLENBQUMsVUFBVSxzQkFBc0IsRUFDN0UscUZBQXFGLENBQ3RGLENBQUM7WUFDSixDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBR08saUJBQWlCLENBQUMsSUFBYztRQUN0QyxNQUFNLE9BQU8sR0FBVyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDeEMsTUFBTSxLQUFLLEdBQTRCLE9BQU8sQ0FBQyxLQUFLLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDcEUsSUFBSSxDQUFDLEtBQUssSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQ3hCLE9BQU8sRUFBRSxDQUFDO1FBQ1osQ0FBQztRQUFBLENBQUM7UUFDRixPQUFPLEtBQUssQ0FBQyxDQUFDLENBQUM7YUFDWixLQUFLLENBQUMsR0FBRyxDQUFDO2FBQ1YsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7YUFDaEQsTUFBTSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxJQUFJLElBQUksS0FBSyxFQUFFLENBQUMsQ0FBQztJQUN6QyxDQUFDO0lBRU8sUUFBUSxDQUFDLFNBQWlCLEVBQUUsS0FBYSxFQUFFLFVBQW1CO1FBQ3BFLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7WUFDNUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDOUIsQ0FBQztRQUNELElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxVQUFVLEVBQUUsQ0FBQyxDQUFDO0lBQ2hFLENBQUM7SUFFTyxVQUFVLENBQUMsU0FBaUI7UUFDbEMsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDO1lBQ25DLE1BQU0sSUFBSSxLQUFLLENBQUMsR0FBRyxTQUFTLHVDQUF1QyxDQUFDLENBQUM7UUFDdkUsQ0FBQztJQUNILENBQUM7SUFHTyxlQUFlLENBQUMsRUFBWTtRQUNsQyxPQUFPLE9BQU8sRUFBRSxLQUFLLFVBQVUsSUFBSSxDQUFDLENBQUMsRUFBRSxDQUFDLFNBQVMsSUFBSSxFQUFFLENBQUMsU0FBUyxDQUFDLFdBQVcsS0FBSyxFQUFFLENBQUM7SUFDdkYsQ0FBQztDQUNGIn0=