yasui
Version:
Lightweight Express-based framework for REST and web APIs
122 lines • 13 kB
JavaScript
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVjb3JhdG9yLXZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9kZWNvcmF0b3ItdmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxlQUFlLEVBQUUsV0FBVyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQzVELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDckQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBaUIzQyxNQUFNLE9BQU8sa0JBQWtCO0lBR0E7SUFGckIsTUFBTSxDQUFvQztJQUVsRCxZQUE2QixTQUFzQjtRQUF0QixjQUFTLEdBQVQsU0FBUyxDQUFhO1FBQ2pELElBQUksQ0FBQyxNQUFNLEdBQUcsRUFBRSxDQUFDO0lBQ25CLENBQUM7SUFFTSxZQUFZO1FBQ2pCLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQztZQUNyQixPQUFPO1FBQ1QsQ0FBQztRQUNELElBQUksUUFBUSxHQUFXLG1DQUFtQyxDQUFDO1FBQzNELEtBQUssTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUNwRCxRQUFRLElBQUksU0FBUyxHQUFHLENBQUMsU0FBUyxLQUFLLEdBQUcsQ0FBQyxLQUFLLEdBQUcsQ0FBQztZQUNwRCxJQUFJLEdBQUcsQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFDbkIsUUFBUSxJQUFJLFlBQVksR0FBRyxDQUFDLFVBQVUsR0FBRyxDQUFDO1lBQzVDLENBQUM7UUFDSCxDQUFDO1FBQ0QsSUFBSSxhQUFhLEVBQUUsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDdEMsQ0FBQztJQUVNLFFBQVE7UUFDYixP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