/* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ import {isValidator, IValidator, ValidationResult, Shape, PropertyValidator} from "./index"; export class ShapeValidator implements IValidator { constructor(private shape: Shape) { } go(value: unknown, key?: string) : ValidationResult { const err: (msg: string) => ValidationResult = msg => ({ success: false, message: msg }); // First check that the value is an object and not an array, null, undefined, etc if (value === null || value === undefined) { return err((key ?? 'value') + " cannot be null or undefined."); } else if (Array.isArray(value)) { return err((key ?? 'value') + " must be an object but was an array."); } else if (typeof value !== "object") { return err((key ?? 'value') + ` must be an object but was ${typeof value}.`); } // Get the keys of both the expected shape, and the value const expectedKeys = Object.getOwnPropertyNames(this.shape); const actualKeys = Object.keys(value); // Check if any expected property is missing from the value for (let expected of expectedKeys) { if (actualKeys.indexOf(expected) === -1) { //return err(`Value is missing expected property ${expected}.`); } } // All properties are accounted for! Now loop through each validator on the expected shape and test the value for (let expected of expectedKeys) { const result : ValidationResult = this.validateShape(expected as keyof T, value, key); if (result.success === false) return result; } // All validation passed! return { success: true, value: value as T } } private validateShape(key: keyof T, value: unknown, parentKey?: string) : ValidationResult { const validator = this.shape[key]; const propertyValue: unknown = value[key]; let propertyKey = ''; if(typeof parentKey !== 'undefined') propertyKey += parentKey+'.'; propertyKey += key; return this.evaluateValidator(validator, propertyValue, propertyKey); } protected evaluateValidator(propertyValidator: PropertyValidator, propertyValue: unknown, propertyKey: string) : ValidationResult { const result : ValidationResult | IValidator | boolean = isValidator(propertyValidator) ? propertyValidator.go(propertyValue, propertyKey) : propertyValidator(propertyValue, propertyKey); if(typeof result === 'boolean') { if(!result) return {success: false, message: propertyKey + ' is not valid.'}; } else { if(isValidator(result)) { return this.evaluateValidator(result, propertyValue, propertyKey); } else { if (result.success === false) return result; } } return {success: true, value: propertyValue as T}; } }