UNPKG

tidecloak-js

Version:

TideCloak client side JS SDK

109 lines (102 loc) 4.72 kB
import RuleEngineService from "./RuleEngineService.js"; /** * Process the rule settings for the specified key. * A successful rule set is one where all of its conditions pass (filter rules are applied first, then non-filter). * When multiple successful rule sets are found, if any include a rule with field "Outputs.Amount" that has * a condition with method "TOTAL_LESS_THAN", we select the rule set whose condition value (converted to a number) * is the lowest. * * @param {string} key - The key to select validation settings. * @param {string} id - The substring that must be contained in the ruleSetId. * @param {RuleSettings} ruleSettings - The rule settings object. * @param {string} draftJson - The JSON string to evaluate. * @returns {{ roles: string[], threshold: number }} */ export default function processThresholdRules(key, id, ruleSettings, draftJson) { // Retrieve rule sets for the specified key. const ruleSets = ruleSettings.validationSettings[key]; if (!ruleSets || ruleSets.length === 0) { console.warn(`No rule sets found for key "${key}".`); return { roles: [], threshold: 0 }; } // Instantiate the rule engine. const ruleEngine = new RuleEngineService(); let passingRuleSets = []; // Loop through each rule set. for (const ruleSet of ruleSets) { // Only consider rule sets whose ruleSetId contains the specified substring. if (ruleSet.ruleSetId && ruleSet.ruleSetId.includes(id)) { // Evaluate the rule set. // evaluateRules applies its filter rules first and then evaluates the remaining conditions. if (ruleEngine.evaluateRules(ruleSet.rules, draftJson)) { // A ruleset is successful if all of its rules pass. // Get the threshold from outputs (if provided), converting to a number. const threshold = (ruleSet.outputs && ruleSet.outputs["threshold"] !== undefined) ? Number(ruleSet.outputs["threshold"]) : 0; // Derive the role by splitting the ruleSetId; use the last segment. const parts = ruleSet.ruleSetId.split("."); const role = parts[parts.length - 1]; passingRuleSets.push({ role, threshold, ruleSet }); } } } // No rule set passed. if (passingRuleSets.length === 0) { throw new Error("No threshold rules passed"); } // If multiple rule sets passed, try to find those that have a specific condition: // the rule definition on field "Outputs.Amount" that includes a condition with method "TOTAL_LESS_THAN". const passingWithTotalLessThan = passingRuleSets.filter(candidate => { return candidate.ruleSet.rules.some(rule => rule.field === "Outputs.Amount" && rule.conditions && rule.conditions.some(cond => cond.method === "TOTAL_LESS_THAN") ); }); if (passingWithTotalLessThan.length > 0) { // For each candidate, find the lowest processable value from its TOTAL_LESS_THAN condition. const evaluatedCandidates = passingWithTotalLessThan.map(candidate => { let processableValues = []; candidate.ruleSet.rules.forEach(rule => { // Only examine the rule if it applies to the field "Outputs.Amount". if (rule.field === "Outputs.Amount" && rule.conditions) { rule.conditions.forEach(cond => { if ( cond.method === "TOTAL_LESS_THAN" && Array.isArray(cond.values) && cond.values.length > 0 ) { const numVal = Number(cond.values[0]); if (!isNaN(numVal)) { processableValues.push(numVal); } } }); } }); // Determine the lowest processable value for this candidate. const minProcessableValue = processableValues.length > 0 ? Math.min(...processableValues) : Infinity; return { candidate, minProcessableValue }; }); // Sort candidates by the lowest processable value. evaluatedCandidates.sort((a, b) => a.minProcessableValue - b.minProcessableValue); const selected = evaluatedCandidates[0].candidate; const allowedRoles = evaluatedCandidates.map(c => c.candidate.role) return { roles: allowedRoles, threshold: selected.threshold }; } else { // Fallback: If no rule set has the specific TOTAL_LESS_THAN condition, // ensure that all passing rule sets have the same output threshold. const uniqueThresholds = new Set(passingRuleSets.map(rs => rs.threshold)); if (uniqueThresholds.size > 1) { throw new Error("Conflicting thresholds found among passing rule sets."); } const roles = passingRuleSets.map(rs => rs.role); return { roles, threshold: passingRuleSets[0].threshold }; } }