UNPKG

@ivandt/json-rules

Version:

Rule parsing engine for JSON rules

320 lines (319 loc) 18.5 kB
"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; };