vscroll
Version:
Virtual scroll engine
372 lines • 11.7 kB
JavaScript
export var ValidatorType;
(function (ValidatorType) {
ValidatorType["number"] = "must be a number";
ValidatorType["integer"] = "must be an integer";
ValidatorType["integerUnlimited"] = "must be an integer or infinity";
ValidatorType["moreOrEqual"] = "must be a number greater than (or equal to) {arg1}";
ValidatorType["itemList"] = "must be an array of items {arg1}";
ValidatorType["boolean"] = "must be a boolean";
ValidatorType["object"] = "must be an object";
ValidatorType["element"] = "must be an html element";
ValidatorType["function"] = "must be a function";
ValidatorType["funcOfxArguments"] = "must have {arg1} argument(s)";
ValidatorType["funcOfxAndMoreArguments"] = "must have at least {arg1} argument(s)";
ValidatorType["funcOfXToYArguments"] = "must have {arg1} to {arg2} arguments";
ValidatorType["oneOfCan"] = "can be present as only one item of {arg1} list";
ValidatorType["oneOfMust"] = "must be present as only one item of {arg1} list";
ValidatorType["or"] = "must satisfy at least 1 validator from {arg1} list";
ValidatorType["enum"] = "must belong to {arg1} list";
})(ValidatorType || (ValidatorType = {}));
const getError = (msg, args) => (args || ['']).reduce((acc, arg, index) => acc.replace(`{arg${index + 1}}`, arg), msg);
const getNumber = (value) => typeof value === 'number' || (typeof value === 'string' && value !== '')
? Number(value)
: NaN;
const onNumber = (value) => {
const parsedValue = getNumber(value);
const errors = [];
if (Number.isNaN(parsedValue)) {
errors.push(ValidatorType.number);
}
return { value: parsedValue, isSet: true, isValid: !errors.length, errors };
};
const onInteger = (value) => {
const errors = [];
value = getNumber(value);
const parsedValue = parseInt(String(value), 10);
if (value !== parsedValue) {
errors.push(ValidatorType.integer);
}
return { value: parsedValue, isSet: true, isValid: !errors.length, errors };
};
const onIntegerUnlimited = (value) => {
let parsedValue = value;
const errors = [];
value = getNumber(value);
if (!Number.isFinite(value)) {
parsedValue = value;
}
else {
parsedValue = parseInt(String(value), 10);
}
if (value !== parsedValue) {
errors.push(ValidatorType.integerUnlimited);
}
return { value: parsedValue, isSet: true, isValid: !errors.length, errors };
};
const onMoreOrEqual = (limit, fallback) => (value) => {
const result = onNumber(value);
if (!result.isValid) {
return result;
}
let parsedValue = result.value;
const errors = [];
if (parsedValue < limit) {
if (!fallback) {
errors.push(getError(ValidatorType.moreOrEqual, [String(limit)]));
}
else {
parsedValue = limit;
}
}
return { value: parsedValue, isSet: true, isValid: !errors.length, errors };
};
const onBoolean = (value) => {
const errors = [];
let parsedValue = value;
if (value === 'true') {
parsedValue = true;
}
else if (value === 'false') {
parsedValue = false;
}
if (typeof parsedValue !== 'boolean') {
errors.push(ValidatorType.boolean);
}
return { value: parsedValue, isSet: true, isValid: !errors.length, errors };
};
const onObject = (value) => {
const errors = [];
if (!value || Object.prototype.toString.call(value) !== '[object Object]') {
errors.push(ValidatorType.object);
}
return { value, isSet: true, isValid: !errors.length, errors };
};
const onHtmlElement = (value) => {
const errors = [];
if (!(value instanceof Element) && !(value instanceof Document)) {
errors.push(ValidatorType.element);
}
return { value, isSet: true, isValid: !errors.length, errors };
};
const onItemList = (value) => {
let parsedValue = value;
const errors = [];
if (!Array.isArray(value)) {
errors.push(ValidatorType.itemList);
parsedValue = [];
}
else if (!value.length) {
errors.push(getError(ValidatorType.itemList, ['with at least 1 item']));
}
else if (value.length > 1) {
const type = typeof value[0];
for (let i = value.length - 1; i >= 0; i--) {
if (typeof value[i] !== type) {
errors.push(getError(ValidatorType.itemList, ['of items of the same type']));
break;
}
}
}
return { value: parsedValue, isSet: true, isValid: !errors.length, errors };
};
const onFunction = (value) => {
const errors = [];
if (typeof value !== 'function') {
errors.push(ValidatorType.function);
}
return { value: value, isSet: true, isValid: !errors.length, errors };
};
const onFunctionWithXArguments = (argsCount) => (value) => {
const result = onFunction(value);
if (!result.isValid) {
return result;
}
value = result.value;
const errors = [];
if (value.length !== argsCount) {
errors.push(getError(ValidatorType.funcOfxArguments, [String(argsCount)]));
}
return { value: value, isSet: true, isValid: !errors.length, errors };
};
const onFunctionWithXAndMoreArguments = (argsCount) => (value) => {
const result = onFunction(value);
if (!result.isValid) {
return result;
}
value = result.value;
const errors = [];
if (value.length < argsCount) {
errors.push(getError(ValidatorType.funcOfxArguments, [String(argsCount)]));
}
return { value: value, isSet: true, isValid: !errors.length, errors };
};
const onFunctionWithXToYArguments = (from, to) => (value) => {
const result = onFunction(value);
if (!result.isValid) {
return result;
}
value = result.value;
const errors = [];
if (value.length < from || value.length > to) {
errors.push(getError(ValidatorType.funcOfXToYArguments, [String(from), String(to)]));
}
return { value: value, isSet: true, isValid: !errors.length, errors };
};
const onOneOf = (tokens, must) => (value, context) => {
const errors = [];
const isSet = value !== void 0;
let noOneIsPresent = !isSet;
const err = must ? ValidatorType.oneOfMust : ValidatorType.oneOfCan;
if (!Array.isArray(tokens) || !tokens.length) {
errors.push(getError(err, ['undefined']));
}
else {
for (let i = tokens.length - 1; i >= 0; i--) {
const token = tokens[i];
if (typeof token !== 'string') {
errors.push(getError(err, [tokens.join('", "')]) + ' (non-string token)');
break;
}
const isAnotherPresent = context && Object.prototype.hasOwnProperty.call(context, token);
if (isSet && isAnotherPresent) {
errors.push(getError(err, [tokens.join('", "')]) + ` (${token} is present)`);
break;
}
if (noOneIsPresent && isAnotherPresent) {
noOneIsPresent = false;
}
}
if (must && noOneIsPresent) {
errors.push(getError(err, [tokens.join('", "')]));
}
}
return { value, isSet, isValid: !errors.length, errors };
};
const onOr = (validators) => (value) => {
const errors = [];
if (validators.every(validator => !validator.method(value).isValid)) {
errors.push(validators.map(v => v.type).join(' OR '));
}
return { value, isSet: true, isValid: !errors.length, errors };
};
const onEnum = (list) => (value) => {
const errors = [];
const values = Object.keys(list).filter(k => isNaN(Number(k))).map(k => list[k]);
if (!values.some(item => item === value)) {
errors.push(getError(ValidatorType.enum, ['[' + values.join(',') + ']']));
}
return { value, isSet: true, isValid: !errors.length, errors };
};
export const VALIDATORS = {
NUMBER: {
type: ValidatorType.number,
method: onNumber
},
INTEGER: {
type: ValidatorType.integer,
method: onInteger
},
INTEGER_UNLIMITED: {
type: ValidatorType.integerUnlimited,
method: onIntegerUnlimited
},
MORE_OR_EQUAL: (limit, fallback) => ({
type: ValidatorType.moreOrEqual,
method: onMoreOrEqual(limit, fallback)
}),
BOOLEAN: {
type: ValidatorType.boolean,
method: onBoolean
},
OBJECT: {
type: ValidatorType.object,
method: onObject
},
ITEM_LIST: {
type: ValidatorType.itemList,
method: onItemList
},
ELEMENT: {
type: ValidatorType.element,
method: onHtmlElement
},
FUNC: {
type: ValidatorType.function,
method: onFunction
},
FUNC_WITH_X_ARGUMENTS: (count) => ({
type: ValidatorType.funcOfxArguments,
method: onFunctionWithXArguments(count)
}),
FUNC_WITH_X_AND_MORE_ARGUMENTS: (count) => ({
type: ValidatorType.funcOfxAndMoreArguments,
method: onFunctionWithXAndMoreArguments(count)
}),
FUNC_WITH_X_TO_Y_ARGUMENTS: (from, to) => ({
type: ValidatorType.funcOfXToYArguments,
method: onFunctionWithXToYArguments(from, to)
}),
ONE_OF_CAN: (list) => ({
type: ValidatorType.oneOfCan,
method: onOneOf(list, false)
}),
ONE_OF_MUST: (list) => ({
type: ValidatorType.oneOfMust,
method: onOneOf(list, true)
}),
OR: (list) => ({
type: ValidatorType.or,
method: onOr(list)
}),
ENUM: (list) => ({
type: ValidatorType.enum,
method: onEnum(list)
})
};
export class ValidatedData {
constructor(context) {
this.params = {};
this.contextErrors = [];
this.errors = [];
this.isValid = true;
this.setContext(context);
}
setContext(context) {
if (!context || Object.prototype.toString.call(context) !== '[object Object]') {
this.setCommonError('context is not an object');
this.isValidContext = false;
}
else {
this.isValidContext = true;
}
this.context = context;
}
setValidity() {
this.errors = Object.keys(this.params).reduce((acc, key) => [
...acc, ...this.params[key].errors
], []);
this.isValid = !this.errors.length;
}
setCommonError(error) {
this.contextErrors.push(error);
this.errors.push(error);
this.isValid = false;
}
setParam(token, value) {
if (!value.isValid) {
value.errors = !value.isSet
? [`"${token}" must be set`]
: value.errors.map((err) => `"${token}" ${err}`);
}
this.params[token] = value;
this.setValidity();
}
showErrors() {
return this.errors.length
? 'validation failed: ' + this.errors.join(', ')
: '';
}
}
export const runValidator = (current, validator, context) => {
const { value, errors } = current;
const result = validator.method(value, context);
const _errors = [...errors, ...result.errors];
return {
value: result.value,
isSet: result.isSet,
isValid: !_errors.length,
errors: _errors
};
};
const getDefault = (value, prop) => {
const empty = value === void 0;
const auto = !prop.mandatory && prop.defaultValue !== void 0;
return {
value: !empty ? value : (auto ? prop.defaultValue : void 0),
isSet: !empty || auto,
isValid: !empty || !prop.mandatory,
errors: []
};
};
export const validateOne = (context, name, prop) => {
const result = getDefault(context[name], prop);
if (!result.isSet) {
const oneOfMust = prop.validators.find(v => v.type === ValidatorType.oneOfMust);
if (oneOfMust) {
return runValidator(result, oneOfMust, context);
}
}
else {
for (const validator of Object.values(prop.validators)) {
const current = runValidator(result, validator, context);
if (!current.isValid && prop.defaultValue !== void 0) {
return {
value: prop.defaultValue,
isSet: true,
isValid: true,
errors: []
};
}
Object.assign(result, current);
}
}
return result;
};
export const validate = (context, params) => {
const data = new ValidatedData(context);
Object.entries(params).forEach(([key, prop]) => data.setParam(key, data.isValidContext
? validateOne(data.context, key, prop)
: getDefault(void 0, prop)));
return data;
};
//# sourceMappingURL=validation.js.map