json-expression-eval
Version:
json serializable rule engine / boolean expression evaluator
182 lines • 8.89 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.validate = exports.evaluate = void 0;
const typeGuards_1 = require("./typeGuards");
const helpers_1 = require("./helpers");
const extractValueOrRef = (context, validation, valueOrRef) => {
if ((0, typeGuards_1.isWithRef)(valueOrRef)) {
const { value, exists } = (0, helpers_1.getFromPath)(context, valueOrRef.ref);
if (validation && !exists) {
throw new Error(`Invalid expression - unknown context key ${valueOrRef.ref}`);
}
return value;
}
else {
return valueOrRef;
}
};
const computeValue = (context, validation, value, expressionKey) => {
if ((0, typeGuards_1.isMathOp)(value)) {
const lhs = extractValueOrRef(context, validation, value.lhs);
const rhs = extractValueOrRef(context, validation, value.rhs);
(0, helpers_1.expressionNumberAssertion)(expressionKey, lhs);
(0, helpers_1.expressionNumberAssertion)(expressionKey, rhs);
switch (value.op) {
case '+':
return lhs + rhs;
case '-':
return lhs - rhs;
case '*':
return lhs * rhs;
case '/':
return lhs / rhs;
case '%':
return lhs % rhs;
case 'pow':
return Math.pow(lhs, rhs);
default:
throw new Error(`Invalid expression - ${expressionKey} has invalid math operand ${value.op}`);
}
}
return extractValueOrRef(context, validation, value);
};
async function evaluateCompareOp(expressionValue, expressionKey, contextValue, context, validation) {
if (!(0, typeGuards_1._isObject)(expressionValue)) {
return contextValue === expressionValue;
}
const compareKeys = (0, helpers_1.objectKeys)(expressionValue);
if (compareKeys.length !== 1) {
throw new Error('Invalid expression - too may keys');
}
if ((0, typeGuards_1.isEqualCompareOp)(expressionValue)) {
return contextValue === computeValue(context, validation, expressionValue.eq, expressionKey);
}
else if ((0, typeGuards_1.isNotEqualCompareOp)(expressionValue)) {
return contextValue !== computeValue(context, validation, expressionValue.neq, expressionKey);
}
else if ((0, typeGuards_1.isInqCompareOp)(expressionValue)) {
return expressionValue.inq.map((value) => computeValue(context, validation, value, expressionKey))
.indexOf(contextValue) >= 0;
}
else if ((0, typeGuards_1.isNinCompareOp)(expressionValue)) {
return expressionValue.nin.map((value) => computeValue(context, validation, value, expressionKey))
.indexOf(contextValue) < 0;
}
else if ((0, typeGuards_1.isRegexCompareOp)(expressionValue)) {
(0, helpers_1.contextStringAssertion)(expressionKey, contextValue);
const regexpValue = computeValue(context, validation, expressionValue.regexp, expressionKey);
(0, helpers_1.expressionStringAssertion)(expressionKey, regexpValue);
return Boolean(contextValue.match(new RegExp(regexpValue)));
}
else if ((0, typeGuards_1.isRegexiCompareOp)(expressionValue)) {
(0, helpers_1.contextStringAssertion)(expressionKey, contextValue);
const regexpiValue = computeValue(context, validation, expressionValue.regexpi, expressionKey);
(0, helpers_1.expressionStringAssertion)(expressionKey, regexpiValue);
return Boolean(contextValue.match(new RegExp(regexpiValue, `i`)));
}
else if ((0, typeGuards_1.isGtCompareOp)(expressionValue)) {
(0, helpers_1.contextNumberAssertion)(expressionKey, contextValue);
const gtValue = computeValue(context, validation, expressionValue.gt, expressionKey);
(0, helpers_1.expressionNumberAssertion)(expressionKey, gtValue);
return contextValue > gtValue;
}
else if ((0, typeGuards_1.isGteCompareOp)(expressionValue)) {
(0, helpers_1.contextNumberAssertion)(expressionKey, contextValue);
const gteValue = computeValue(context, validation, expressionValue.gte, expressionKey);
(0, helpers_1.expressionNumberAssertion)(expressionKey, gteValue);
return contextValue >= gteValue;
}
else if ((0, typeGuards_1.isLteCompareOp)(expressionValue)) {
(0, helpers_1.contextNumberAssertion)(expressionKey, contextValue);
const lteValue = computeValue(context, validation, expressionValue.lte, expressionKey);
(0, helpers_1.expressionNumberAssertion)(expressionKey, lteValue);
return contextValue <= lteValue;
}
else if ((0, typeGuards_1.isLtCompareOp)(expressionValue)) {
(0, helpers_1.contextNumberAssertion)(expressionKey, contextValue);
const ltValue = computeValue(context, validation, expressionValue.lt, expressionKey);
(0, helpers_1.expressionNumberAssertion)(expressionKey, ltValue);
return contextValue < ltValue;
}
else if ((0, typeGuards_1.isBetweenCompareOp)(expressionValue)) {
(0, helpers_1.contextNumberAssertion)(expressionKey, contextValue);
if (expressionValue.between.length !== 2) {
throw new Error(`Invalid expression - ${expressionKey}.length must be 2`);
}
const [lowRaw, highRaw] = expressionValue.between;
const low = computeValue(context, validation, lowRaw, expressionKey);
const high = computeValue(context, validation, highRaw, expressionKey);
(0, helpers_1.expressionNumberAssertion)(`${expressionKey}[0]`, low);
(0, helpers_1.expressionNumberAssertion)(`${expressionKey}[1]`, high);
if (low > high) {
throw new Error(`Invalid expression - ${expressionKey} first value is higher than second value`);
}
return contextValue >= low && contextValue <= high;
}
else {
return (0, helpers_1.assertUnreachable)(expressionValue, `Invalid expression - unknown op ${compareKeys[0]}`);
}
}
async function handleAndOp(andExpression, context, functionsTable, validation, runOptions) {
if (andExpression.length === 0) {
throw new Error('Invalid expression - and operator must have at least one expression');
}
for (const currExpression of andExpression) {
const result = await run(currExpression, context, functionsTable, validation, runOptions);
if (!validation && !result) {
return false;
}
}
return true;
}
async function handleOrOp(orExpression, context, functionsTable, validation, runOptions) {
if (orExpression.length === 0) {
throw new Error('Invalid expression - or operator must have at least one expression');
}
for (const currExpression of orExpression) {
const result = await run(currExpression, context, functionsTable, validation, runOptions);
if (!validation && result) {
return true;
}
}
return false;
}
async function run(expression, context, functionsTable, validation, runOptions) {
const expressionKeys = (0, helpers_1.objectKeys)(expression);
if (expressionKeys.length !== 1) {
throw new Error('Invalid expression - too may keys');
}
const expressionKey = expressionKeys[0];
if ((0, typeGuards_1.isAndCompareOp)(expression)) {
return handleAndOp(expression.and, context, functionsTable, validation, runOptions);
}
else if ((0, typeGuards_1.isOrCompareOp)(expression)) {
return handleOrOp(expression.or, context, functionsTable, validation, runOptions);
}
else if ((0, typeGuards_1.isNotCompareOp)(expression)) {
return !(await run(expression.not, context, functionsTable, validation, runOptions));
}
else if ((0, typeGuards_1.isFunctionCompareOp)(expression, functionsTable, expressionKey)) {
return functionsTable[expressionKey](expression[expressionKey], context, {
custom: runOptions,
validation,
});
}
else {
const { value: contextValue, exists } = (0, helpers_1.getFromPath)(context, expressionKey);
if (validation && !exists) {
throw new Error(`Invalid expression - unknown context key ${expressionKey}`);
}
return evaluateCompareOp(expression[expressionKey], expressionKey, contextValue, context, validation);
}
}
const evaluate = async (expression, context, functionsTable, runOptions) => {
return run(expression, context, functionsTable, false, runOptions);
};
exports.evaluate = evaluate;
// Throws in case of validation error. Does not run functions or compare fields
const validate = async (expression, validationContext, functionsTable, runOptions) => {
await run(expression, validationContext, functionsTable, true, runOptions);
};
exports.validate = validate;
//# sourceMappingURL=evaluator.js.map