rulepilot
Version:
Rule parsing engine for JSON rules
213 lines (212 loc) • 8.83 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_validateCondition, _Validator_validateConstraint, _Validator_isValidCondition;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Validator = void 0;
const object_discovery_1 = require("./object-discovery");
class Validator {
constructor() {
_Validator_instances.add(this);
_Validator_objectDiscovery.set(this, new object_discovery_1.ObjectDiscovery());
}
/**
* 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_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_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 = [
"==",
"!=",
">",
"<",
">=",
"<=",
"in",
"not in",
"contains",
"not contains",
"contains any",
"not contains any",
"matches",
"not matches",
];
if (!operators.includes(constraint.operator)) {
return {
isValid: false,
error: {
message: 'Constraint "operator" has invalid type.',
element: constraint,
},
};
}
if (constraint.value === null &&
!["==", "!=", "contains", "not contains"].includes(constraint.operator)) {
return {
isValid: false,
error: {
message: '"operator" must be in ["==", "!=", "contains", "not contains"] if "value" is null.',
element: constraint,
},
};
}
// We must check that the value is an array if the operator is 'in' or 'not in'.
if (["in", "not in", "contains any", "not contains any"].includes(constraint.operator) &&
!Array.isArray(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)) {
try {
new RegExp(constraint.value);
}
catch (e) {
return {
isValid: false,
error: {
message: 'Constraint "value" must be a valid regular expression if the "operator" is in ["matches", "not matches"]',
element: constraint,
},
};
}
}
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 };
};