@qavajs/validation
Version:
Lib that transform plain english definition to validation functions
185 lines • 8.12 kB
JavaScript
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
;