import { ConfigurationInterface } from './Entities/ConfigurationInterface.js'; import { ValidationResponseInterface } from './Entities/ValidationResponseInterface.js'; import { ValidationFieldInterface } from './Entities/ValidationFieldInterface.js'; import { ContextEntityInterface } from './Entities/ContextEntityInterface.js'; export class Validator { private readonly _validatorObject: ValidationResponseInterface; private readonly _validationFields: Array; constructor(configuration: ConfigurationInterface) { this._validatorObject = { errors: [], }; /** * Supported rules: 'required', 'number', 'min:', 'max:', 'object', 'instanceOf:', 'boolean'. */ this._validationFields = [ { name: 'token', rules: ['required'], }, { name: 'context', rules: ['required', 'object', 'instanceOf:ContextEntityInterface'], }, { name: 'width', rules: ['number', 'min:1'], }, { name: 'height', rules: ['number', 'min:1'], }, { name: 'showHeader', rules: ['boolean'], }, { name: 'showLivechat', rules: ['boolean'], }, ]; this.validate(configuration); } public getValidation(): ValidationResponseInterface { return this._validatorObject; } private validate(configuration: ConfigurationInterface): void { this.validateRequiredFields(configuration); this.validateNumberFields(configuration); this.validateObjectFields(configuration); this.validateBooleanFields(configuration); } private validateRequiredFields(configuration: ConfigurationInterface): void { const requiredFields: Array = this._validationFields.filter(rule => rule.rules.includes('required')); requiredFields.forEach((field: ValidationFieldInterface) => { if (!Validator.getConfigurationField(configuration, field.name)) { this._validatorObject.errors.push( `The ${field.name} field is required.` ); } }); } private validateNumberFields(configuration: ConfigurationInterface): void { const numberFields: Array = this._validationFields.filter(rule => rule.rules.includes('number')); numberFields.forEach((field: ValidationFieldInterface) => { const numberField: undefined | string | number | object | boolean = Validator.getConfigurationField(configuration, field.name); if (numberField !== undefined) { this.validateNumber(field, numberField); } }); } private static getConfigurationField( configuration: ConfigurationInterface, fieldName: string ): undefined | string | number | object | boolean { return configuration[fieldName as keyof ConfigurationInterface]; } private validateNumber( field: ValidationFieldInterface, value: number | string | object | boolean ): void { if (typeof value !== 'number') { this._validatorObject.errors.push( `The ${field.name} field should be a number.` ); return; } this.validateNumberRange(field, value); } private validateNumberRange( field: ValidationFieldInterface, value: number ): void { const ruleMin: string | undefined = field.rules.find(rule => rule.startsWith('min') ); const ruleMinValue: number | undefined = ruleMin !== undefined ? Number(ruleMin.substring(4, ruleMin.length)) : undefined; const validatesMinRange: boolean = ruleMinValue !== undefined; const ruleMax: string | undefined = field.rules.find(rule => rule.startsWith('max') ); const ruleMaxValue: number | undefined = ruleMax !== undefined ? Number(ruleMax.substring(4, ruleMax.length)) : undefined; const validatesMaxRange: boolean = ruleMaxValue !== undefined; const minValueIsOutOfBound = Validator.minValueIsOutOfBound( validatesMinRange, ruleMinValue, value ); const maxValueIsOutOfBound = Validator.maxValueIsOutOfBound( validatesMaxRange, ruleMaxValue, value ); if (minValueIsOutOfBound) { this._validatorObject.errors.push( `The ${field.name} should be at least ${ruleMinValue}.` ); return; } if (maxValueIsOutOfBound) { this._validatorObject.errors.push( `The ${field.name} should be at most ${ruleMaxValue}.` ); } } private static minValueIsOutOfBound( shouldValidate: boolean, minBound: number | undefined, value: number ): boolean { return shouldValidate && minBound !== undefined && value < minBound; } private static maxValueIsOutOfBound( shouldValidate: boolean, maxBound: number | undefined, value: number ): boolean { return shouldValidate && maxBound !== undefined && value > maxBound; } private validateObjectFields(configuration: ConfigurationInterface): void { const objectFields: Array = this._validationFields.filter(rule => rule.rules.includes('object')); objectFields.forEach((field: ValidationFieldInterface) => { const objectField: undefined | string | number | object | boolean = Validator.getConfigurationField(configuration, field.name); if (objectField) { this.validateObject(field, objectField); } }); } private validateObject( field: ValidationFieldInterface, value: number | string | object | boolean ): void { if (typeof value !== 'object') { this._validatorObject.errors.push( `The ${field.name} field should be an object.` ); return; } this.validateObjectInstance(field, value); } private validateObjectInstance( field: ValidationFieldInterface, value: object ): void { const instanceOfRule: string | undefined = field.rules.find(rule => rule.startsWith('instanceOf:') ); if (!instanceOfRule) { return; } const instanceClass: string = instanceOfRule.substring( 11, instanceOfRule.length ); const instanceValidators: Record = { ContextEntityInterface: () => Validator.objectIsInstanceOfContextEntityInterface(value), }; if ( instanceValidators[instanceClass] && !instanceValidators[instanceClass]() ) { this._validatorObject.errors.push( `The ${field.name} field should be valid object.` ); } } private static objectIsInstanceOfContextEntityInterface( obj: object ): obj is ContextEntityInterface { return 'id' in obj && 'type' in obj; } private validateBooleanFields(configuration: ConfigurationInterface): void { const booleanFields: Array = this._validationFields.filter(rule => rule.rules.includes('boolean')); booleanFields.forEach((field: ValidationFieldInterface) => { const booleanField: undefined | string | number | object | boolean = Validator.getConfigurationField(configuration, field.name); if (booleanField !== undefined) { this.validateBoolean(field, booleanField); } }); } private validateBoolean( field: ValidationFieldInterface, value: number | string | object | boolean ): void { if (typeof value !== 'boolean') { this._validatorObject.errors.push( `The ${field.name} field should be a boolean.` ); } } }