@sergtyapkin/models-validator
Version:
 
273 lines (272 loc) • 12.3 kB
JavaScript
;
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 }));
}