UNPKG

@qavajs/validation

Version:

Lib that transform plain english definition to validation functions

185 lines 8.12 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validationRegexp = exports.validationExtractRegexp = exports.validations = exports.SoftAssertionError = void 0; exports.verify = verify; exports.getValidation = getValidation; exports.getPollValidation = getPollValidation; exports.poll = poll; const chai_1 = require("chai"); const ajv_1 = __importDefault(require("ajv")); class SoftAssertionError extends chai_1.AssertionError { constructor() { super(...arguments); this.name = 'SoftAssertionError'; } } exports.SoftAssertionError = SoftAssertionError; chai_1.Assertion.addMethod('notStrictEqual', function (ER) { const obj = this._obj; this.assert(obj == ER, 'expected #{this} to equal #{exp}', 'expected #{this} to not equal #{exp}', ER, obj); }); chai_1.Assertion.addMethod('caseInsensitiveEqual', function (ER) { const obj = this._obj; this.assert(obj.toLowerCase() == ER.toLowerCase(), 'expected #{this} to equal #{exp}', 'expected #{this} to not equal #{exp}', ER, obj); }); chai_1.Assertion.addMethod('matchSchema', function (schema) { var _a; const obj = this._obj; const ajv = new ajv_1.default(); const validate = ajv.compile(schema); const isValid = validate(obj); const messages = validate.errors ? (_a = validate.errors) === null || _a === void 0 ? void 0 : _a.map(err => `${err.instancePath} ${err.message} (${err.schemaPath})`) : []; const errors = [ 'object does not match schema', ...messages ].join('\n'); this.assert(isValid, errors, 'expected #{this} to not match schema #{exp}', '', ''); }); exports.validations = { EQUAL: 'equal', DEEPLY_EQUAL: 'deeply equal', STRICTLY_EQUAL: 'strictly equal', HAVE_MEMBERS: 'have member', MATCH: 'match', CONTAIN: 'contain', ABOVE: 'above', BELOW: 'below', GREATER: 'greater than', LESS: 'less than', HAVE_TYPE: 'have type', INCLUDE_MEMBERS: 'include member', HAVE_PROPERTY: 'have property', MATCH_SCHEMA: 'match schema', CASE_INSENSITIVE_EQUAL: 'case insensitive equal', }; const isClause = '(?:is |do |does |to )?'; const notClause = '(?<reverse>not |to not )?'; const toBeClause = '(?:to )?(?:be )?'; const softlyClause = '(?<soft>softly )?'; const validationClause = `(?:(?<validation>${Object.values(exports.validations).join('|')})(?:s|es| to)?)`; exports.validationExtractRegexp = new RegExp(`^${isClause}${notClause}${toBeClause}${softlyClause}${validationClause}$`); exports.validationRegexp = new RegExp(`(${isClause}${notClause}${toBeClause}${softlyClause}${validationClause})`); const aboveFn = (expectClause, ER) => expectClause.above(toNumber(ER)); const belowFn = (expectClause, ER) => expectClause.below(toNumber(ER)); const validationFns = { [exports.validations.EQUAL]: (expectClause, ER) => expectClause.notStrictEqual(ER), [exports.validations.STRICTLY_EQUAL]: (expectClause, ER) => expectClause.equal(ER), [exports.validations.DEEPLY_EQUAL]: (expectClause, ER) => expectClause.eql(ER), [exports.validations.HAVE_MEMBERS]: (expectClause, ER) => expectClause.have.members(ER), [exports.validations.MATCH]: (expectClause, ER) => expectClause.match(toRegexp(ER)), [exports.validations.CONTAIN]: (expectClause, ER) => expectClause.contain(ER), [exports.validations.ABOVE]: aboveFn, [exports.validations.BELOW]: belowFn, [exports.validations.GREATER]: aboveFn, [exports.validations.LESS]: belowFn, [exports.validations.HAVE_TYPE]: (expectClause, ER) => expectClause.a(ER), [exports.validations.INCLUDE_MEMBERS]: (expectClause, ER) => expectClause.include.members(ER), [exports.validations.HAVE_PROPERTY]: (expectClause, ER) => expectClause.have.property(ER), [exports.validations.MATCH_SCHEMA]: (expectClause, ER) => expectClause.matchSchema(ER), [exports.validations.CASE_INSENSITIVE_EQUAL]: (expectClause, ER) => expectClause.caseInsensitiveEqual(ER), }; /** * Basic verification function * @param {VerifyInput} object with all needed data for validation */ function verify({ AR, ER, validation, reverse, soft }) { const prefix = 'Fail'; const expectClause = reverse ? (0, chai_1.expect)(AR, prefix).to.not : (0, chai_1.expect)(AR, prefix).to; const validate = validationFns[validation]; try { validate(expectClause, ER); } catch (err) { if (soft && err instanceof Error) throw new SoftAssertionError(err.message, { cause: err }); throw err; } } function getValidation(validationType, options) { const match = exports.validationExtractRegexp.exec(validationType); if (!match) throw new Error(`Validation '${validationType}' is not supported`); const { reverse, validation, soft } = match.groups; const softProp = (options === null || options === void 0 ? void 0 : options.soft) || !!soft; return function (AR, ER) { verify({ AR, ER, validation, reverse: Boolean(reverse), soft: softProp }); }; } function getPollValidation(validationType, options) { const match = exports.validationExtractRegexp.exec(validationType); if (!match) throw new Error(`Poll validation '${validationType}' is not supported`); const { reverse, validation, soft } = match.groups; const softProp = (options === null || options === void 0 ? void 0 : options.soft) || !!soft; return async function (AR, ER, options) { var _a, _b; const timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : 5000; const interval = (_b = options === null || options === void 0 ? void 0 : options.interval) !== null && _b !== void 0 ? _b : 500; let lastError = new Error(`Promise was not settled before timeout`); let intervalId; const evaluatePromise = new Promise(resolve => { intervalId = setInterval(async () => { try { const actualValue = await AR(); verify({ AR: actualValue, ER, validation, reverse: Boolean(reverse), soft: softProp }); clearInterval(intervalId); resolve(); } catch (err) { lastError = err; } }, interval); }); const timeoutPromise = new Promise((_, reject) => setTimeout(() => { clearInterval(intervalId); reject(lastError); }, timeout)); return Promise.race([evaluatePromise, timeoutPromise]); }; } async function poll(fn, options) { var _a, _b; const timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : 5000; const interval = (_b = options === null || options === void 0 ? void 0 : options.interval) !== null && _b !== void 0 ? _b : 500; let lastError = new Error('Unexpected error'); let intervalId; const evaluatePromise = new Promise(resolve => { intervalId = setInterval(async () => { try { await fn(); clearInterval(intervalId); resolve(); } catch (err) { lastError = err; } }, interval); }); const timeoutPromise = new Promise((_, reject) => setTimeout(() => { clearInterval(intervalId); reject(lastError); }, timeout)); return Promise.race([evaluatePromise, timeoutPromise]); } function toNumber(n) { const parsedNumber = parseFloat(n); if (Number.isNaN(parsedNumber)) { throw new Error(`${n} is not a number`); } return parsedNumber; } function toRegexp(r) { return r instanceof RegExp ? r : new RegExp(r); } //# sourceMappingURL=verify.js.map