UNPKG

vscroll

Version:
372 lines 11.7 kB
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