@ivandt/json-rules
Version:
Rule parsing engine for JSON rules
320 lines (319 loc) • 18.5 kB
JavaScript
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Evaluator_instances, _Evaluator_objectDiscovery, _Evaluator_templateParser, _Evaluator_nestedResults, _Evaluator_evaluateRule, _Evaluator_evaluateCondition, _Evaluator_processNestedResults, _Evaluator_accumulate, _Evaluator_createRegExp, _Evaluator_isBetween, _Evaluator_isBefore, _Evaluator_isAfter, _Evaluator_isOnOrBefore, _Evaluator_isOnOrAfter, _Evaluator_startsWith, _Evaluator_endsWith, _Evaluator_arrayContains, _Evaluator_checkConstraint, _Evaluator_isBetweenNumbers, _Evaluator_isBetweenDates, _Evaluator_isEmpty;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Evaluator = void 0;
const validators_1 = require("./validators");
const template_parser_1 = require("./template-parser");
const object_discovery_1 = require("./object-discovery");
class Evaluator {
constructor() {
_Evaluator_instances.add(this);
_Evaluator_objectDiscovery.set(this, new object_discovery_1.ObjectDiscovery());
_Evaluator_templateParser.set(this, new template_parser_1.TemplateParser());
/** Stores any results from nested conditions */
_Evaluator_nestedResults.set(this, void 0);
}
/**
* Evaluates a rule against a set of criteria and returns the result.
* If the criteria is an array (indicating multiple criteria to test),
* the rule will be evaluated against each item in the array and
* an array of results will be returned.
*
* @param rule The rule to evaluate.
* @param criteria The criteria to evaluate the rule against.
*/
evaluate(rule, criteria) {
if (criteria instanceof Array) {
const result = [];
for (const c of criteria) {
// Clear any previous sub-results.
__classPrivateFieldSet(this, _Evaluator_nestedResults, [], "f");
result.push(__classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_evaluateRule).call(this, rule.conditions, c, rule === null || rule === void 0 ? void 0 : rule.default));
}
return __classPrivateFieldGet(this, _Evaluator_nestedResults, "f").length
? __classPrivateFieldGet(this, _Evaluator_nestedResults, "f")[0]
: result;
}
// Clear any previous sub-results.
__classPrivateFieldSet(this, _Evaluator_nestedResults, [], "f");
const e = __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_evaluateRule).call(this, rule.conditions, criteria, rule === null || rule === void 0 ? void 0 : rule.default);
return __classPrivateFieldGet(this, _Evaluator_nestedResults, "f").length ? __classPrivateFieldGet(this, _Evaluator_nestedResults, "f")[0] : e;
}
}
exports.Evaluator = Evaluator;
_Evaluator_objectDiscovery = new WeakMap(), _Evaluator_templateParser = new WeakMap(), _Evaluator_nestedResults = new WeakMap(), _Evaluator_instances = new WeakSet(), _Evaluator_evaluateRule = function _Evaluator_evaluateRule(conditions, criteria, defaultResult, isSubRule = false) {
var _a;
// Cater for the case where the conditions property is not an array.
conditions = conditions instanceof Array ? conditions : [conditions];
// We should evaluate all conditions and return the result
// of the first condition that passes.
for (const condition of conditions) {
const result = __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_evaluateCondition).call(this, condition, criteria);
if (result)
return isSubRule ? true : (_a = condition === null || condition === void 0 ? void 0 : condition.result) !== null && _a !== void 0 ? _a : true;
}
// If no conditions pass, we should return the default result of
// the rule or false if no default result is provided.
return defaultResult !== null && defaultResult !== void 0 ? defaultResult : false;
}, _Evaluator_evaluateCondition = function _Evaluator_evaluateCondition(condition, criteria) {
// The condition must have an 'any' or 'all' property.
const type = __classPrivateFieldGet(this, _Evaluator_objectDiscovery, "f").conditionType(condition);
if (!type)
return false;
// If the condition has nested results
__classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_processNestedResults).call(this, condition, criteria, type);
// Set the starting result
let result = undefined;
// Check each node in the condition.
for (const node of condition[type]) {
// Ignore sub-rules when evaluating the condition.
if (__classPrivateFieldGet(this, _Evaluator_objectDiscovery, "f").isConditionWithResult(node))
continue;
let fn;
if (__classPrivateFieldGet(this, _Evaluator_objectDiscovery, "f").isCondition(node))
fn = () => __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_evaluateCondition).call(this, node, criteria);
if (__classPrivateFieldGet(this, _Evaluator_objectDiscovery, "f").isConstraint(node))
fn = () => __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_checkConstraint).call(this, node, criteria);
// Process the node
result = __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_accumulate).call(this, type, fn, result);
}
return result;
}, _Evaluator_processNestedResults = function _Evaluator_processNestedResults(condition, criteria, type) {
// Find all the nested conditions which have results
const candidates = condition[type].filter((node) => __classPrivateFieldGet(this, _Evaluator_objectDiscovery, "f").isConditionWithResult(node));
// For each candidate, check if all the sibling
// conditions and constraints pass
candidates.forEach((candidate) => {
let siblingsPass = undefined;
for (const node of condition[type]) {
if (__classPrivateFieldGet(this, _Evaluator_objectDiscovery, "f").isConditionWithResult(node))
continue;
if (__classPrivateFieldGet(this, _Evaluator_objectDiscovery, "f").isCondition(node)) {
siblingsPass = __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_accumulate).call(this, type, () => __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_evaluateCondition).call(this, node, criteria), siblingsPass);
}
if (__classPrivateFieldGet(this, _Evaluator_objectDiscovery, "f").isConstraint(node)) {
siblingsPass = __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_accumulate).call(this, type, () => __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_checkConstraint).call(this, node, criteria), siblingsPass);
}
}
if (!siblingsPass)
return;
// Evaluate the sub-rule
const passed = __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_evaluateRule).call(this, candidate, criteria, false, true);
if (passed)
__classPrivateFieldGet(this, _Evaluator_nestedResults, "f").push(candidate.result);
});
}, _Evaluator_accumulate = function _Evaluator_accumulate(type, fn, result) {
result = undefined === result ? ["all", "none"].includes(type) : result;
switch (type) {
case "any":
result = result || fn();
break;
case "all":
result = result && fn();
break;
case "none":
result = result && !fn();
}
return result;
}, _Evaluator_createRegExp = function _Evaluator_createRegExp(value) {
if (value && typeof value === "object" && "regex" in value) {
return new RegExp(value.regex, value.flags || "");
}
throw new Error("Invalid regex pattern format - must be a RegexPattern object");
}, _Evaluator_isBetween = function _Evaluator_isBetween(value, range) {
if (!Array.isArray(range) || range.length !== 2)
return false;
const [min, max] = range;
// Check for number range
if (typeof value === "number" &&
typeof min === "number" &&
typeof max === "number") {
return value >= min && value <= max;
}
// Check for date range
if (value instanceof Date && min instanceof Date && max instanceof Date) {
return value >= min && value <= max;
}
return false;
}, _Evaluator_isBefore = function _Evaluator_isBefore(a, b) {
return a instanceof Date && b instanceof Date && a < b;
}, _Evaluator_isAfter = function _Evaluator_isAfter(a, b) {
return a instanceof Date && b instanceof Date && a > b;
}, _Evaluator_isOnOrBefore = function _Evaluator_isOnOrBefore(a, b) {
return a instanceof Date && b instanceof Date && a <= b;
}, _Evaluator_isOnOrAfter = function _Evaluator_isOnOrAfter(a, b) {
return a instanceof Date && b instanceof Date && a >= b;
}, _Evaluator_startsWith = function _Evaluator_startsWith(a, b) {
return typeof a === "string" && typeof b === "string" && a.startsWith(b);
}, _Evaluator_endsWith = function _Evaluator_endsWith(a, b) {
return typeof a === "string" && typeof b === "string" && a.endsWith(b);
}, _Evaluator_arrayContains = function _Evaluator_arrayContains(array, value) {
return Array.isArray(array) && array.includes(value);
}, _Evaluator_checkConstraint = function _Evaluator_checkConstraint(constraint, criteria) {
// If the value contains '.' we should assume it is a nested property
const criterion = constraint.field.includes(".")
? __classPrivateFieldGet(this, _Evaluator_objectDiscovery, "f").resolveNestedProperty(constraint.field, criteria)
: criteria[constraint.field];
// If the criteria object does not have the field we are looking for,
// we should return false UNLESS it's an "empty" check operator
if (undefined === criterion &&
!["is empty", "is not empty"].includes(constraint.operator)) {
return false;
}
// Resolve template variables in the constraint value (if it exists)
const resolvedValue = "value" in constraint
? __classPrivateFieldGet(this, _Evaluator_templateParser, "f").resolveTemplateValue(constraint.value, criteria)
: undefined;
switch (constraint.operator) {
case "is equal":
return criterion == resolvedValue;
case "is not equal":
return criterion != resolvedValue;
case "is greater than":
return criterion > resolvedValue;
case "is greater than or equal":
return criterion >= resolvedValue;
case "is less than":
return criterion < resolvedValue;
case "is less than or equal":
return criterion <= resolvedValue;
case "in":
return (Array.isArray(resolvedValue) &&
resolvedValue.includes(criterion));
case "not in":
return (!Array.isArray(resolvedValue) ||
!resolvedValue.includes(criterion));
case "contains":
return (typeof criterion === "string" &&
typeof resolvedValue === "string" &&
criterion.includes(resolvedValue));
case "not contains":
return (typeof criterion === "string" &&
typeof resolvedValue === "string" &&
!criterion.includes(resolvedValue));
case "contains any":
return (typeof criterion === "string" &&
Array.isArray(resolvedValue) &&
resolvedValue.some((x) => typeof x === "string" && criterion.includes(x)));
case "not contains any":
return (typeof criterion === "string" &&
Array.isArray(resolvedValue) &&
!resolvedValue.some((x) => typeof x === "string" && criterion.includes(x)));
case "matches":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_createRegExp).call(this, resolvedValue).test(`${criterion}`);
case "not matches":
return !__classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_createRegExp).call(this, resolvedValue).test(`${criterion}`);
case "is between numbers":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_isBetweenNumbers).call(this, criterion, resolvedValue);
case "is between dates":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_isBetweenDates).call(this, criterion, resolvedValue);
case "is not between numbers":
return !__classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_isBetweenNumbers).call(this, criterion, resolvedValue);
case "is not between dates":
return !__classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_isBetweenDates).call(this, criterion, resolvedValue);
case "is before":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_isBefore).call(this, criterion, resolvedValue);
case "is after":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_isAfter).call(this, criterion, resolvedValue);
case "is on or before":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_isOnOrBefore).call(this, criterion, resolvedValue);
case "is on or after":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_isOnOrAfter).call(this, criterion, resolvedValue);
case "starts with":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_startsWith).call(this, criterion, resolvedValue);
case "ends with":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_endsWith).call(this, criterion, resolvedValue);
case "array contains":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_arrayContains).call(this, criterion, resolvedValue);
case "array no contains":
return !__classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_arrayContains).call(this, criterion, resolvedValue);
// Math validators
case "is even":
return (typeof criterion === "number" &&
Number.isFinite(criterion) &&
criterion % 2 === 0);
case "is odd":
return (typeof criterion === "number" &&
Number.isFinite(criterion) &&
criterion % 2 !== 0);
case "is positive":
return (typeof criterion === "number" &&
Number.isFinite(criterion) &&
criterion > 0);
case "is negative":
return (typeof criterion === "number" &&
Number.isFinite(criterion) &&
criterion < 0);
// Empty validators
case "is empty":
return __classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_isEmpty).call(this, criterion);
case "is not empty":
return !__classPrivateFieldGet(this, _Evaluator_instances, "m", _Evaluator_isEmpty).call(this, criterion);
// Advanced validators
case "is valid email":
return (0, validators_1.validateEmail)(criterion, resolvedValue);
case "is valid phone":
return (0, validators_1.validatePhone)(criterion, resolvedValue);
case "is URL":
return (0, validators_1.validateURL)(criterion, resolvedValue);
case "is UUID":
return (0, validators_1.validateUUID)(criterion, resolvedValue);
case "is EAN":
return (0, validators_1.validateEAN)(criterion);
case "is IMEI":
return (0, validators_1.validateIMEI)(criterion, resolvedValue);
case "is unit":
return (0, validators_1.validateUnit)(criterion, resolvedValue);
case "is country":
return (0, validators_1.validateCountry)(criterion, resolvedValue);
case "is domain":
return (0, validators_1.validateDomain)(criterion, resolvedValue);
default:
const exhaustiveCheck = constraint;
throw new Error(`Unknown operator: ${exhaustiveCheck.operator}`);
}
}, _Evaluator_isBetweenNumbers = function _Evaluator_isBetweenNumbers(value, range) {
if (!Array.isArray(range) || range.length !== 2)
return false;
const [min, max] = range;
return (typeof value === "number" &&
typeof min === "number" &&
typeof max === "number" &&
value >= min &&
value <= max);
}, _Evaluator_isBetweenDates = function _Evaluator_isBetweenDates(value, range) {
if (!Array.isArray(range) || range.length !== 2)
return false;
const [min, max] = range;
return (value instanceof Date &&
min instanceof Date &&
max instanceof Date &&
value >= min &&
value <= max);
}, _Evaluator_isEmpty = function _Evaluator_isEmpty(value) {
// null or undefined
if (value === null || value === undefined) {
return true;
}
// Empty string (but not zero)
if (typeof value === "string" && value === "") {
return true;
}
// Empty array
if (Array.isArray(value) && value.length === 0) {
return true;
}
// All other values (including 0, false, empty objects) are not empty
return false;
};