@gisce/conscheck
Version:
A JavaScript library for evaluating JSON-based conditions. It allows complex conditional logic to be defined and interpreted from JSON structures, enabling dynamic decision-making in applications.
137 lines (124 loc) • 3.68 kB
text/typescript
export type Operator =
| "="
| "=="
| ">="
| "<="
| ">"
| "<"
| "!="
| "<>"
| "in"
| "not in";
export interface Rule {
field: string;
operator: Operator;
value: string | number | boolean;
}
export interface Condition {
condition: "AND" | "OR";
rules: Array<Rule | NestedCondition>;
}
type NestedCondition = Condition;
export type FieldComparisonParams = {
fieldName: string;
valueInObject: any;
expectedValue: any;
};
export type FieldComparisonResult = {
modifiedValueInObject?: any;
modifiedExpectedValue?: any;
directOutcome?: boolean;
};
const getValue = (
obj: Record<string, any>,
valueOrField: string | number | boolean,
): any => {
if (isField(valueOrField)) {
const fieldName = (valueOrField as string).slice(1);
if (fieldName in obj) {
return obj[fieldName];
}
}
return valueOrField;
};
const isField = (value: string | number | boolean): boolean =>
typeof value === "string" && value.startsWith("$");
export const evaluateCondition = ({
object,
condition,
evaluateFieldComparison,
}: {
object: Record<string, any>;
condition: Rule | NestedCondition;
evaluateFieldComparison?: (
args: FieldComparisonParams,
) => FieldComparisonResult;
}): boolean => {
if ("field" in condition && "operator" in condition && "value" in condition) {
const fieldName = condition.field;
let valueInObject = object[fieldName];
let expectedValue = getValue(object, condition.value);
// Prepare parameters for evaluateFieldComparison
const comparisonParams: FieldComparisonParams = {
fieldName,
valueInObject,
expectedValue,
};
// Evaluate the field comparison if the callback is provided
const comparisonResult = evaluateFieldComparison?.(comparisonParams) || {
directOutcome: undefined,
modifiedValueInObject: null,
modifiedExpectedValue: null,
};
if (comparisonResult?.directOutcome !== undefined) {
return comparisonResult.directOutcome;
}
if (comparisonResult?.modifiedValueInObject !== null) {
valueInObject = comparisonResult?.modifiedValueInObject;
}
if (comparisonResult?.modifiedExpectedValue !== null) {
expectedValue = comparisonResult?.modifiedExpectedValue;
}
switch (condition.operator) {
case "=":
case "==":
return valueInObject == expectedValue;
case ">=":
return valueInObject >= expectedValue;
case "<=":
return valueInObject <= expectedValue;
case ">":
return valueInObject > expectedValue;
case "<":
return valueInObject < expectedValue;
case "<>":
case "!=":
return valueInObject != expectedValue;
case "in":
return expectedValue.includes(valueInObject);
case "not in":
return !expectedValue.includes(valueInObject);
default:
throw new Error(
`Unsupported operator: ${condition.operator as string}`,
);
}
} else if ("condition" in condition && "rules" in condition) {
const nestedCondition = condition;
if (nestedCondition.condition === "AND") {
return nestedCondition.rules.every((rule) =>
evaluateCondition({ object, condition: rule, evaluateFieldComparison }),
);
} else if (nestedCondition.condition === "OR") {
return nestedCondition.rules.some((rule) =>
evaluateCondition({ object, condition: rule, evaluateFieldComparison }),
);
} else {
throw new Error(
`Unsupported condition type: ${nestedCondition.condition as string}`,
);
}
} else {
throw new Error("Invalid condition format");
}
};