UNPKG

json-expression-eval

Version:

json serializable rule engine / boolean expression evaluator

212 lines 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.evaluateWithReason = 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(new RegExp(regexpValue).exec(contextValue)); } 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(new RegExp(regexpiValue, `i`).exec(contextValue)); } else if ((0, typeGuards_1.isExistsCompareOp)(expressionValue)) { const shouldExist = expressionValue.exists; const doesExist = contextValue !== null && contextValue !== undefined; return shouldExist ? doesExist : !doesExist; } 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'); } const reasons = []; for (const currExpression of andExpression) { const runResult = await run(currExpression, context, functionsTable, validation, runOptions); if (!runResult.result && !validation) { // AND fails on first false const reason = { and: [runResult.reason] }; return { result: false, reason }; } reasons.push(runResult.reason); } return { result: true, reason: { and: reasons }, }; } 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'); } const reasons = []; for (const currExpression of orExpression) { const runResult = await run(currExpression, context, functionsTable, validation, runOptions); if (runResult.result && !validation) { // OR succeeds on first true const reason = { or: [runResult.reason] }; return { result: true, reason }; } reasons.push(runResult.reason); } return { result: false, reason: { or: reasons }, }; } 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)) { const innerResult = await run(expression.not, context, functionsTable, validation, runOptions); return { result: !innerResult.result, reason: { not: innerResult.reason }, }; } else if ((0, typeGuards_1.isFunctionCompareOp)(expression, functionsTable, expressionKey)) { const result = await functionsTable[expressionKey](expression[expressionKey], context, { custom: runOptions, validation, }); return { result, reason: expression }; } else { const { value: contextValue, exists } = (0, helpers_1.getFromPath)(context, expressionKey); if (validation && !exists) { throw new Error(`Invalid expression - unknown context key ${expressionKey}`); } const result = await evaluateCompareOp(expression[expressionKey], expressionKey, contextValue, context, validation); return { result, reason: expression }; } } const evaluate = async (expression, context, functionsTable, runOptions) => { const runResult = await run(expression, context, functionsTable, false, runOptions); return runResult.result; }; 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; const evaluateWithReason = async (expression, context, functionsTable, runOptions) => { return run(expression, context, functionsTable, false, runOptions); }; exports.evaluateWithReason = evaluateWithReason; //# sourceMappingURL=evaluator.js.map