UNPKG

@sergtyapkin/models-validator

Version:

![Run tests](https://github.com/SergTyapkin/js-models-validator/workflows/Run%20tests/badge.svg) ![downloads](https://img.shields.io/npm/dt/%40sergtyapkin%2Fmodels-validator)

273 lines (272 loc) 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateModel = validateModel; exports.reverseValidateModel = reverseValidateModel; exports.generateSnakeCaseFromCamelCaseModel = generateSnakeCaseFromCamelCaseModel; exports.generateCamelCaseFromSnakeCaseModel = generateCamelCaseFromSnakeCaseModel; exports.ArrayType = ArrayType; exports.ObjectType = ObjectType; exports.Type = Type; const SIMPLE_TYPES_CONSTRUCTORS = [String, Number, BigInt, Symbol, Boolean]; function throwFieldNotExists(type, stackTrace, stackTraceFrom) { // @ts-ignore throw TypeError(`Field ${stackTrace} ${stackTrace !== stackTraceFrom ? `searched in ${stackTraceFrom}` : ''} not exists in provided data. Expecting type: ${JSON.stringify(type) || (type === null || type === void 0 ? void 0 : type.name) || type}`); } function throwParseError(expectedType, realValue, stackTrace, stackTraceFrom) { throw TypeError(`Field ${stackTrace} ${stackTrace !== stackTraceFrom ? `searched in ${stackTraceFrom}` : ''} doesn\'t match type in declared model. Expected type: ${expectedType.name}. Gotten: ${JSON.stringify(realValue)}`); } function throwEnumError(allowedValuesSet, gottenValue, stackTrace, stackTraceFrom) { throw TypeError(`Field ${stackTrace} ${stackTrace !== stackTraceFrom ? `searched in ${stackTraceFrom}` : ''} value not allowed in Enum. Allowed one of this values: ${JSON.stringify(Array.from(allowedValuesSet))}. Gotten: ${JSON.stringify(gottenValue)}`); } function throwDeclaringError(stackTrace) { throw SyntaxError(`Error in declaring model field "${stackTrace}"`); } function parseFieldSimpleType(dataValue, type, stackTrace, stackTraceFrom) { let res; try { if (SIMPLE_TYPES_CONSTRUCTORS.includes(type)) { // @ts-ignore res = type(dataValue); // use functional type construction for simple types } else if (type === Object) { // "any" type res = dataValue; } else { res = new type(dataValue); // use class constructor for another types } } catch (_a) { throwParseError(type, dataValue, stackTrace, stackTraceFrom); } if (typeof res === 'number' && (isNaN(res) && !Number.isNaN(dataValue))) { throwParseError(type, dataValue, stackTrace, stackTraceFrom); } return res; } function parseArray(dataValue, typesArray, stackTrace, stackTraceFrom, isReverse) { const res = []; typesArray.forEach((innerType, idx) => { parseField(res, dataValue[idx], innerType, String(idx), undefined, undefined, `${stackTrace}[${idx}]`, `${stackTraceFrom}[${idx}]`, isReverse); }); return res; } function parseUnlimitedArray(dataValue, itemType, stackTrace, stackTraceFrom, isReverse) { if (!itemType) { throwDeclaringError(stackTrace); } const res = []; dataValue.forEach((dataValueItem, idx) => { // @ts-ignore parseField(res, dataValueItem, itemType, String(idx), itemType.optional, itemType.default, `${stackTrace}[${idx}]`, `${stackTraceFrom}[${idx}]`, isReverse); }); return res; } function parseSet(dataValue, type, stackTrace, stackTraceFrom) { // Straight equality if (type.has(dataValue)) { return dataValue; } else { // Simple type // Trying to found its type directly let result = null; type.forEach(oneOfTypes => { if (!(oneOfTypes instanceof Function)) { return; } try { const res = parseFieldSimpleType(dataValue, oneOfTypes, '', ''); if (typeof res === typeof dataValue) { result = res; } } catch (_a) { } }); if (result !== null) { return result; } } throwEnumError(type, dataValue, stackTrace, stackTraceFrom); } function parseLongDeclaring(resultObject, dataValue, key, type, stackTrace, stackTraceFrom, isReverse) { var _a; const longDeclaringType = type.type; if (!longDeclaringType) { throwDeclaringError(stackTrace); } if (Array.isArray(longDeclaringType)) { // long limited array declaring resultObject[key] = parseArray(dataValue, longDeclaringType, stackTrace, stackTraceFrom, isReverse); return; } if (longDeclaringType === Array) { // long unlimited array declaring const longDeclaringItem = type.item; resultObject[key] = parseUnlimitedArray(dataValue, longDeclaringItem, stackTrace, stackTraceFrom, isReverse); return; } if (longDeclaringType instanceof Set) { // long set declaring resultObject[key] = parseSet(dataValue, longDeclaringType, stackTrace, stackTraceFrom); return; } if (longDeclaringType === Object) { // long field declaring const longDeclaringModel = type.fields; // @ts-ignore const targetKey = isReverse ? ((_a = longDeclaringType.from) !== null && _a !== void 0 ? _a : key) : key; resultObject[targetKey] = {}; if (!longDeclaringModel) { // unlimited Object with any fields if (typeof dataValue !== 'object') { throwParseError(longDeclaringType, dataValue, stackTrace, stackTraceFrom); return; } resultObject[targetKey] = dataValue; return; } parseFields(resultObject[targetKey], longDeclaringModel, dataValue, stackTrace, stackTraceFrom, isReverse); return; } resultObject[key] = parseFieldSimpleType(dataValue, longDeclaringType, stackTrace, stackTraceFrom); // long simple type declaring } function parseField(resultObject, dataValue, type, key, optional = false, defaultValue = undefined, stackTrace, stackTraceFrom, isReverse) { // Assert field existing (optional) if (dataValue === undefined || dataValue === null) { // Field not exists or equals undefined or null if (optional) { // Field not exists and optional if (defaultValue !== undefined) { // Field not exists, optional, but has default value resultObject[key] = defaultValue; return; } else { return; // Field not exists, optional, and hasn't default value -> skip } } else { throwFieldNotExists(type, stackTrace, stackTraceFrom); // Field not exists but must be exists } } // Parse field by it's type if (type instanceof Function) { // short declaring of any type resultObject[key] = parseFieldSimpleType(dataValue, type, stackTrace, stackTraceFrom); return; } if (Array.isArray(type)) { // short array declaring resultObject[key] = parseArray(dataValue, type, stackTrace, stackTraceFrom, isReverse); return; } if (type instanceof Set) { // short set declaring resultObject[key] = parseSet(dataValue, type, stackTrace, stackTraceFrom); return; } if (type instanceof Object) { // long any type declaring parseLongDeclaring(resultObject, dataValue, key, type, stackTrace, stackTraceFrom, isReverse); return; } throwDeclaringError(stackTrace); } function parseFields(resultObject, model, data, stackTrace, stackTraceFrom, isReverse = false) { Object.getOwnPropertyNames(model).forEach(key => { var _a, _b, _c; const type = model[key]; // @ts-ignore let dataValue = data[isReverse ? key : ((_a = type.from) !== null && _a !== void 0 ? _a : key)]; // @ts-ignore const optional = type.optional; // @ts-ignore const defaultValue = type.default; // @ts-ignore const targetKey = isReverse ? ((_b = type.from) !== null && _b !== void 0 ? _b : key) : key; // @ts-ignore parseField(resultObject, dataValue, type, targetKey, optional, defaultValue, `${stackTrace}.${key}`, `${stackTraceFrom}.${(_c = type.from) !== null && _c !== void 0 ? _c : key}`, isReverse); }); } // ------- MODELS VALIDATORS --------- function validateModel(model, data) { if (typeof data === 'string') { try { data = JSON.parse(data); } catch (err) { throw TypeError(`Second argument "data" cannot be parsed from string. Error:\n${err}`); } } if (typeof data !== 'object' || data === null) { throw TypeError('Second argument "data" is not valid type. Must be Object or String'); } if (typeof model !== 'object' || model === null) { throw TypeError('First argument "model" must be Object'); } const resultObject = {}; parseFields(resultObject, model, data, '<object>', '<object>'); return resultObject; } function reverseValidateModel(model, data) { if (typeof data !== 'object' || data === null) { throw TypeError('Second argument "data" is not valid type. Must be Object or String'); } if (typeof model !== 'object' || model === null) { throw TypeError('First argument "model" must be Object'); } const resultObject = {}; parseFields(resultObject, model, data, '<object>', '<object>', true); return resultObject; } // ------- MODELS GENERATORS --------- function camelCaseToSnakeCase(str) { return str.replace(/([a-z](?=[A-Z][a-zA-Z])|[A-Z](?=[A-Z][a-z])|[0-9](?=[A-Za-z])|[a-zA-Z](?=[0-9]))/g, '$1_').toLowerCase(); } function snakeCaseToCamelCase(str) { return str.replace(/_([^_])/g, (_, word) => word[0].toUpperCase() + word.slice(1)); } function generateModelWithChangedKeys(model, keyChangingFoo) { function getChangedFieldType(type, key) { const changedKey = keyChangingFoo(key); if (type instanceof Function || Array.isArray(type) || type instanceof Set) { // short declaring return changedKey === key ? type : { type: type, from: changedKey, }; } // long declaring return Object.assign(changedKey !== key ? { from: changedKey } : {}, type, // @ts-ignore type.item ? { item: getChangedFieldType(type.item, '') } : {}, // @ts-ignore type.fields ? { fields: generateModelWithChangedKeys(type.fields, keyChangingFoo) } : {}); } const resultModel = {}; Object.getOwnPropertyNames(model).forEach(key => { // @ts-ignore const type = model[key]; resultModel[key] = getChangedFieldType(type, key); }); // @ts-ignore return resultModel; } function generateSnakeCaseFromCamelCaseModel(model) { return generateModelWithChangedKeys(model, camelCaseToSnakeCase); } function generateCamelCaseFromSnakeCaseModel(model) { return generateModelWithChangedKeys(model, snakeCaseToCamelCase); } // ------ MODELS SHORTCUTS ----- function ArrayType(type, optional = false, defaultValue = undefined) { if (type instanceof Function || Array.isArray(type) || type instanceof Set) { // short declaring return Object.assign(Object.assign({ type: Array, item: type }, (optional && { optional: true })), (defaultValue !== undefined && { default: defaultValue })); } if (typeof type === 'object' && type !== null) { // object declaring return Object.assign(Object.assign({ type: Array, // @ts-ignore item: ObjectType(type) }, (optional && { optional: true })), (defaultValue !== undefined && { default: defaultValue })); } throw SyntaxError(`Error in declaring ArrayType. Expected type declaration. Provided: "${type}"`); } function ObjectType(fields, optional = false, defaultValue = undefined) { if (typeof fields !== 'object' || fields === null) { throw SyntaxError(`Error in declaring ObjectType. Expected object with fields declaration. Provided: "${fields}"`); } return Object.assign(Object.assign({ type: Object, fields: fields }, (optional && { optional: true })), (defaultValue !== undefined && { default: defaultValue })); } function Type(type, optional = false, defaultValue = undefined) { return Object.assign(Object.assign({ type: type }, (optional && { optional: true })), (defaultValue !== undefined && { default: defaultValue })); }