@ivandt/json-rules
Version:
Rule parsing engine for JSON rules
320 lines (319 loc) • 13.6 kB
JavaScript
"use strict";
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 _Validator_instances, _Validator_objectDiscovery, _Validator_templateParser, _Validator_validateCondition, _Validator_validateRegexPattern, _Validator_validateConstraint, _Validator_isValidCondition, _Validator_validateTemplateVariables;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Validator = void 0;
const template_parser_1 = require("./template-parser");
const object_discovery_1 = require("./object-discovery");
class Validator {
constructor() {
_Validator_instances.add(this);
_Validator_objectDiscovery.set(this, new object_discovery_1.ObjectDiscovery());
_Validator_templateParser.set(this, new template_parser_1.TemplateParser());
}
/**
* Takes in a rule as a parameter and returns a boolean indicating whether the rule is valid or not.
* @param rule The rule to validate.
*/
validate(rule) {
var _a;
// Assume the rule is valid.
const result = { isValid: true };
// Check the rule is a valid JSON
if (!__classPrivateFieldGet(this, _Validator_objectDiscovery, "f").isObject(rule)) {
return {
isValid: false,
error: {
message: "The rule must be a valid JSON object.",
element: rule,
},
};
}
// Cater for the case where the conditions property is not an array.
const conditions = rule.conditions instanceof Array ? rule.conditions : [rule.conditions];
// Validate the 'conditions' property.
if (conditions.length === 0 ||
(__classPrivateFieldGet(this, _Validator_objectDiscovery, "f").isObject(conditions[0]) &&
!Object.keys(conditions[0]).length)) {
return {
isValid: false,
error: {
message: "The conditions property must contain at least one condition.",
element: rule,
},
};
}
// Validate each condition in the rule.
for (const condition of conditions) {
const subResult = __classPrivateFieldGet(this, _Validator_instances, "m", _Validator_validateCondition).call(this, condition);
result.isValid = result.isValid && subResult.isValid;
result.error = (_a = result === null || result === void 0 ? void 0 : result.error) !== null && _a !== void 0 ? _a : subResult === null || subResult === void 0 ? void 0 : subResult.error;
}
return result;
}
}
exports.Validator = Validator;
_Validator_objectDiscovery = new WeakMap(), _Validator_templateParser = new WeakMap(), _Validator_instances = new WeakSet(), _Validator_validateCondition = function _Validator_validateCondition(condition, depth = 0) {
var _a, _b;
// Check to see if the condition is valid.
const result = __classPrivateFieldGet(this, _Validator_instances, "m", _Validator_isValidCondition).call(this, condition);
if (!result.isValid)
return result;
// Set the type of condition.
const type = __classPrivateFieldGet(this, _Validator_objectDiscovery, "f").conditionType(condition);
// Check if the condition is iterable
if (!Array.isArray(condition[type])) {
return {
isValid: false,
error: {
message: `The condition '${type}' should be iterable.`,
element: condition,
},
};
}
// Check if the condition is iterable
if (!condition[type].length) {
return {
isValid: false,
error: {
message: `The condition '${type}' should not be empty.`,
element: condition,
},
};
}
// Validate each item in the condition.
for (const node of condition[type]) {
// The object should be a condition or constraint.
const isCondition = __classPrivateFieldGet(this, _Validator_objectDiscovery, "f").isCondition(node);
if (isCondition) {
const subResult = __classPrivateFieldGet(this, _Validator_instances, "m", _Validator_validateCondition).call(this, node, depth + 1);
result.isValid = result.isValid && subResult.isValid;
result.error = (_a = result === null || result === void 0 ? void 0 : result.error) !== null && _a !== void 0 ? _a : subResult === null || subResult === void 0 ? void 0 : subResult.error;
}
const isConstraint = __classPrivateFieldGet(this, _Validator_objectDiscovery, "f").isConstraint(node);
if (isConstraint) {
const subResult = __classPrivateFieldGet(this, _Validator_instances, "m", _Validator_validateConstraint).call(this, node);
result.isValid = result.isValid && subResult.isValid;
result.error = (_b = result === null || result === void 0 ? void 0 : result.error) !== null && _b !== void 0 ? _b : subResult === null || subResult === void 0 ? void 0 : subResult.error;
}
if (!isConstraint && !isCondition) {
return {
isValid: false,
error: {
message: "Each node should be a condition or a constraint.",
element: node,
},
};
}
// If any part fails validation there is no point to continue.
if (!result.isValid)
break;
}
return result;
}, _Validator_validateRegexPattern = function _Validator_validateRegexPattern(value) {
if (value && typeof value === "object" && "regex" in value) {
if (typeof value.regex !== "string") {
return { isValid: false, error: "RegexPattern.regex must be a string" };
}
if (value.flags !== undefined && typeof value.flags !== "string") {
return { isValid: false, error: "RegexPattern.flags must be a string" };
}
try {
new RegExp(value.regex, value.flags || "");
return { isValid: true };
}
catch (e) {
return {
isValid: false,
error: "Invalid regular expression pattern or flags",
};
}
}
return {
isValid: false,
error: "Value must be a RegexPattern object with regex and optional flags properties",
};
}, _Validator_validateConstraint = function _Validator_validateConstraint(constraint) {
if ("string" !== typeof constraint.field) {
return {
isValid: false,
error: {
message: 'Constraint "field" must be of type string.',
element: constraint,
},
};
}
const operators = [
"is equal",
"is not equal",
"is greater than",
"is less than",
"is greater than or equal",
"is less than or equal",
"in",
"not in",
"contains",
"not contains",
"contains any",
"not contains any",
"matches",
"not matches",
"is between numbers",
"is between dates",
"is not between numbers",
"is not between dates",
"is before",
"is after",
"is on or before",
"is on or after",
"starts with",
"ends with",
"array contains",
"array no contains",
"is even",
"is odd",
"is positive",
"is negative",
"is empty",
"is not empty",
"is valid email",
"is valid phone",
"is URL",
"is UUID",
"is EAN",
"is IMEI",
"is unit",
"is country",
"is domain",
];
if (!operators.includes(constraint.operator)) {
return {
isValid: false,
error: {
message: 'Constraint "operator" has invalid type.',
element: constraint,
},
};
}
if (constraint.value === null &&
![
"is equal",
"is not equal",
"contains",
"not contains",
"array contains",
"array no contains",
"is even",
"is odd",
"is positive",
"is negative",
"is empty",
"is not empty",
"is EAN",
"is valid email",
"is domain",
"is IMEI",
].includes(constraint.operator)) {
return {
isValid: false,
error: {
message: '"operator" must be in ["is equal", "is not equal", "contains", "not contains", "array contains", "array no contains", "is even", "is odd", "is positive", "is negative", "is empty", "is not empty", "is EAN", "is valid email", "is domain", "is IMEI"] if "value" is null.',
element: constraint,
},
};
}
// We must check that the value is an array if the operator is 'in' or 'not in'.
// Skip this check if the value contains template variables (they will be resolved at runtime)
if (["in", "not in", "contains any", "not contains any"].includes(constraint.operator) &&
!Array.isArray(constraint.value) &&
!__classPrivateFieldGet(this, _Validator_templateParser, "f").hasTemplateVariables(constraint.value)) {
return {
isValid: false,
error: {
message: 'Constraint "value" must be an array if the "operator" is in ["in", "not in", "contains any", "not contains any"]',
element: constraint,
},
};
}
if (["matches", "not matches"].includes(constraint.operator)) {
const regexValidation = __classPrivateFieldGet(this, _Validator_instances, "m", _Validator_validateRegexPattern).call(this, constraint.value);
if (!regexValidation.isValid) {
return {
isValid: false,
error: {
message: `Constraint "value" must be a valid RegexPattern object if the "operator" is in ["matches", "not matches"]. ${regexValidation.error}`,
element: constraint,
},
};
}
}
// Validate template variables in the constraint value
const templateValidation = __classPrivateFieldGet(this, _Validator_instances, "m", _Validator_validateTemplateVariables).call(this, constraint);
if (!templateValidation.isValid) {
return templateValidation;
}
return { isValid: true };
}, _Validator_isValidCondition = function _Validator_isValidCondition(obj) {
// Otherwise, the object should be a condition.
if (!__classPrivateFieldGet(this, _Validator_objectDiscovery, "f").isCondition(obj)) {
return {
isValid: false,
error: {
message: "Invalid condition structure.",
element: obj,
},
};
}
const isAny = "any" in obj;
const isAll = "all" in obj;
const isNone = "none" in obj;
// A valid condition must have an 'any', 'all', or 'none' property,
// but cannot have more than one.
if ((isAny && isAll) || (isAny && isNone) || (isAll && isNone)) {
return {
isValid: false,
error: {
message: 'A condition cannot have more than one "any", "all", or "none" property.',
element: obj,
},
};
}
return { isValid: true };
}, _Validator_validateTemplateVariables = function _Validator_validateTemplateVariables(constraint, criteria) {
if (!__classPrivateFieldGet(this, _Validator_templateParser, "f").hasTemplateVariables(constraint.value)) {
return { isValid: true };
}
const variables = __classPrivateFieldGet(this, _Validator_templateParser, "f").extractTemplateVariables(constraint.value);
// Validate template syntax
for (const variable of variables) {
// Check if variable name is valid (only letters, numbers, dots, underscores)
// Must start with letter or underscore, and dots can only be between valid identifiers
if (!/^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/.test(variable.name)) {
return {
isValid: false,
error: {
message: `Invalid template variable name: "${variable.name}". Variable names must start with a letter or underscore and contain only letters, numbers, dots, and underscores.`,
element: constraint,
},
};
}
}
// If criteria is provided, validate that all template variables exist
if (criteria) {
const validation = __classPrivateFieldGet(this, _Validator_templateParser, "f").validateTemplateVariables(constraint.value, criteria);
if (!validation.isValid) {
return {
isValid: false,
error: {
message: `Template variables not found in criteria: ${validation.missingFields.join(", ")}`,
element: constraint,
},
};
}
}
return { isValid: true };
};