libs/ngx-pfe/pfe-conditions/rules-evaluator/rules-evaluator.ts
This PfeRulesEvaluator is used to evaluate the rules used in the navigation configuration.
Methods |
|
Static evaluateAdvancedConditions | ||||||||||||
evaluateAdvancedConditions(conditionAdvanced: ConditionAdvanced, state: PfeUserInputState)
|
||||||||||||
Evaluate Advanced Conditions
Parameters :
Returns :
boolean
true if all conditions are true, false if at least one is false. |
Static evaluateCondition | ||||||||||||
evaluateCondition(condition: ExpressionCondition, state: PfeUserInputState)
|
||||||||||||
Evaluates a condition. Possible comparator are: =, !=, >, < A condition always consists of three parts. For example: a = b Conditions can also contain strings with whitespace: a = 'test with space'
Parameters :
Returns :
boolean
boolean that shows, if the condition was true. |
Static evaluateConditions | ||||||||||||
evaluateConditions(conditions: RulesEvaluatorConditions, state: PfeUserInputState)
|
||||||||||||
Evaluates all the conditions of a navigational rule. Conditions are connected by AND.
Parameters :
Returns :
boolean
true if all conditions are true, false if at least one is false. |
Static evaluateORConditions | ||||||||||||
evaluateORConditions(conditions: ExpressionCondition[], state: PfeUserInputState)
|
||||||||||||
Evaluates all the conditions of a navigational rule. Conditions are connected by OR.
Parameters :
Returns :
boolean
true if all conditions are true, false if at least one is false. |
Static getReplacer | ||||||||
getReplacer(state: PfeUserInputState)
|
||||||||
Returns a replacer function that replaces jsonpath expressions with their corresponding values.
Parameters :
Returns :
(match: string) => any
The replacer function. |
Static getStatePropertyName | ||||||||
getStatePropertyName(conditions: ExpressionCondition[])
|
||||||||
Retrieve the state property names of the first part of the conditions. propertyName in case of $state.propertyName or value unchanged
Parameters :
Returns :
string[]
The state property names of the condition's first part. |
import { parseScript } from 'esprima-next';
import ESTree from 'estree';
import evaluate from 'static-eval';
import { ExpressionCondition, ConditionAdvanced, OPERATORS, RulesEvaluatorConditions } from './condition.model';
import { PfeUtilService } from '../../pfe-util/services/util.service';
import { PfeUserInputState } from '../../models/pfe-state/user-input-state.model';
import { isObject, isString } from '../../util/type-checks';
// used for iterating over all capture groups with content {.*}
export const JSON_PATH_REGEX = /({.*?})/gm;
/**
* This PfeRulesEvaluator is used to evaluate the rules used in the navigation
* configuration.
*
* @export
*/
export class PfeRulesEvaluator {
/**
* Evaluates all the conditions of a navigational rule.
* Conditions are connected by AND.
*
* @param conditions The conditions to check.
* @param state The current state.
* @returns true if all conditions are true, false if at least one is false.
*/
static evaluateConditions(conditions: RulesEvaluatorConditions, state: PfeUserInputState): boolean {
if (Array.isArray(conditions)) {
let result = false;
if (conditions) {
for (const condition of conditions) {
result = PfeRulesEvaluator.evaluateCondition(condition, state);
if (result === false) {
return result;
}
}
}
return result;
}
return PfeRulesEvaluator.evaluateAdvancedConditions(conditions, state);
}
/**
* Evaluates all the conditions of a navigational rule.
* Conditions are connected by OR.
*
* @param conditions The conditions to check.
* @param state The current state.
* @returns true if all conditions are true, false if at least one is false.
*/
static evaluateORConditions(conditions: ExpressionCondition[], state: PfeUserInputState): boolean {
let result = false;
if (conditions) {
for (const condition of conditions) {
result = PfeRulesEvaluator.evaluateCondition(condition, state);
if (result === true) {
return result;
}
}
}
return result;
}
/**
* Evaluate Advanced Conditions
*
* @param conditionAdvanced object that describe the operator and conditions
* @param state The current state.
* @returns true if all conditions are true, false if at least one is false.
*/
static evaluateAdvancedConditions(conditionAdvanced: ConditionAdvanced, state: PfeUserInputState): boolean {
return conditionAdvanced.operator === OPERATORS.AND
? PfeRulesEvaluator.evaluateConditions(conditionAdvanced.conditions, state)
: PfeRulesEvaluator.evaluateORConditions(conditionAdvanced.conditions, state);
}
/**
* Evaluates a condition.
* Possible comparator are:
* =, !=, >, <
* A condition always consists of three parts. For example:
* a = b
* Conditions can also contain strings with whitespace:
* a = 'test with space'
*
* @param condition The condition to evaluate.
* @param state The current state.
* @returns boolean that shows, if the condition was true.
*/
public static evaluateCondition(condition: ExpressionCondition, state: PfeUserInputState): boolean {
try {
let evalResult = false;
if (condition.expression !== undefined) {
const expression = condition.expression;
if (isString(expression)) {
const replacer = this.getReplacer(state);
let statement = expression.replace(JSON_PATH_REGEX, replacer);
// esprima can not handle undefined.
// So undefined == undefined evaluates to false and even !undefined evaluates to false
// To prevent this we convert any undefined to null.
statement = statement.replace(/undefined/gm, 'null');
const ast = (parseScript(statement).body[0] as ESTree.ExpressionStatement).expression;
evalResult = !!evaluate(ast, {}) as boolean;
} else {
evalResult = !!expression;
}
}
if (condition.value1Expression !== undefined) {
const value1 = condition.value1Expression;
let expression1Result = value1;
// If the first expression is not a string, there it not have get the value
if (isString(value1)) {
const jpe1 = value1 as string;
expression1Result = PfeUtilService.getValueOrList(state, jpe1, true);
// Historically, if there is only one expression the result is not threated as a string
// Is threated as a real boolean
if (!condition.operator && condition.value2Expression === undefined && expression1Result) {
return true;
}
}
if (condition.operator && condition.value2Expression !== undefined) {
const value2 = condition.value2Expression;
let expression2Result = value2;
if (isString(value2)) {
const jpe2 = value2 as string;
expression2Result = PfeUtilService.getValueOrList(state, jpe2, true);
}
// If the "value1AsReal" is not true, then the value is treated as string.
expression1Result = JSON.stringify(String(expression1Result));
expression2Result = JSON.stringify(String(expression2Result));
// We compare the two expressions
const expression = `${expression1Result} ${condition.operator} ${expression2Result}`;
const ast = (parseScript(expression).body[0] as ESTree.ExpressionStatement).expression;
evalResult = evaluate(ast, {}) as boolean;
}
}
return evalResult;
} catch (error) {
console.error('Error evaluating rule', condition, error);
return false;
}
}
/**
* Returns a replacer function that replaces jsonpath expressions with
* their corresponding values.
*
* @param state The current state.
* @returns The replacer function.
*/
public static getReplacer(state: PfeUserInputState) {
return (match: string) => {
// {$.aTestKey} -> $.aTestKey
const jsonPath = match.replace(/{|}/gm, '');
const value = PfeUtilService.getValueOrList(state, jsonPath, true);
if (isString(value)) {
return JSON.stringify(String(value));
} else if (isObject(value) && !Array.isArray(value)) {
// can't serialize and evaluate an object
return '"' + Math.random() + '"';
} else if (Array.isArray(value)) {
// Return the Array as a string
return JSON.stringify(value);
}
return value;
};
}
/**
* Retrieve the state property names of the first part of the conditions.
* propertyName in case of $state.propertyName or value unchanged
*
* @param conditions The conditions to parse.
* @returns The state property names of the condition's first part.
*/
public static getStatePropertyName(conditions: ExpressionCondition[]): string[] {
const propertyNames = [];
const FIRST_KEY_REGEX = /\$\.(.*?)(?:\.|$)/g;
if (conditions) {
for (const condition of conditions) {
const value1 = condition.value1Expression as string;
const match = FIRST_KEY_REGEX.exec(value1);
if (match && match.length > 0) {
propertyNames.push(match[1]);
}
}
}
return propertyNames;
}
}