regevbr/json-expression-eval

View on GitHub
src/lib/engine.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
import {RuleFunctionsTable, Rule, FunctionsTable, Context, ResolvedConsequence, ValidationContext} from '../types';
import {evaluate, validate} from './evaluator';
import {objectKeys} from './helpers';
import {isRuleFunction} from './typeGuards';
import {evaluateEngineConsequence} from './engineConsequenceEvaluator';

async function run<ConsequencePayload, C extends Context,
    RF extends RuleFunctionsTable<C, ConsequencePayload, CustomEngineRuleFuncRunOptions>,
    F extends FunctionsTable<C, CustomEngineRuleFuncRunOptions>,
    Ignore, CustomEngineRuleFuncRunOptions>(rules: Rule<ConsequencePayload, RF, C, F, Ignore,
                                                                  CustomEngineRuleFuncRunOptions>[],
                                            context: C, functionsTable: F, ruleFunctionsTable: RF,
 haltOnFirstMatch: boolean, validation: false, runOptions: CustomEngineRuleFuncRunOptions)
    : Promise<void | ResolvedConsequence<ConsequencePayload>[]>
async function run<ConsequencePayload, C extends Context,
    RF extends RuleFunctionsTable<C, ConsequencePayload, CustomEngineRuleFuncRunOptions>,
    F extends FunctionsTable<C, CustomEngineRuleFuncRunOptions>, Ignore,
    CustomEngineRuleFuncRunOptions>(rules: Rule<ConsequencePayload, RF, C, F,
        Ignore, CustomEngineRuleFuncRunOptions>[], context: ValidationContext<C, Ignore>,
 functionsTable: F, ruleFunctionsTable: RF,
 haltOnFirstMatch: boolean, validation: true, runOptions: CustomEngineRuleFuncRunOptions)
    : Promise<void | ResolvedConsequence<ConsequencePayload>[]>
async function run<ConsequencePayload, C extends Context,
    RF extends RuleFunctionsTable<C, ConsequencePayload, CustomEngineRuleFuncRunOptions>,
    F extends FunctionsTable<C, CustomEngineRuleFuncRunOptions>,
    Ignore, CustomEngineRuleFuncRunOptions>(rules: Rule<ConsequencePayload, RF,
                                                                  C, F, Ignore, CustomEngineRuleFuncRunOptions>[],
                                            context: C | ValidationContext<C, Ignore>, functionsTable: F,
 ruleFunctionsTable: RF, haltOnFirstMatch: boolean, validation: boolean, runOptions: CustomEngineRuleFuncRunOptions)
    : Promise<void | ResolvedConsequence<ConsequencePayload>[]> {
    const errors: ResolvedConsequence<ConsequencePayload>[] = [];
    for (const rule of rules) {
        const keys = objectKeys(rule);
        const key = keys[0];
        if (keys.length === 1 && key && isRuleFunction<ConsequencePayload, C, RF, CustomEngineRuleFuncRunOptions>(
            rule, ruleFunctionsTable, key)) {
            const consequence = await ruleFunctionsTable[key](rule[key], context as C, {
                custom: runOptions,
                validation,
            });
            if (consequence) {
                errors.push(consequence);
                if (haltOnFirstMatch && !validation) {
                    return errors;
                }
            }
        } else {
            if (!rule.condition) {
                throw new Error(`Missing condition for rule`);
            }
            if (!rule.consequence) {
                throw new Error(`Missing consequence for rule`);
            }
            if (validation) {
                await validate<C, F, Ignore, CustomEngineRuleFuncRunOptions>(rule.condition,
                    context as ValidationContext<C, Ignore>, functionsTable, runOptions);
                await evaluateEngineConsequence<ConsequencePayload, C, Ignore>(context as C, rule.consequence);
            } else {
                const ruleApplies = await evaluate<C, F, Ignore, CustomEngineRuleFuncRunOptions>(
                    rule.condition, context as C, functionsTable, runOptions);
                if (ruleApplies) {
                    const consequence =
                        await evaluateEngineConsequence<ConsequencePayload, C, Ignore>(context as C, rule.consequence);
                    errors.push(consequence);
                    if (haltOnFirstMatch) {
                        return errors;
                    }
                }
            }
        }
    }
    return errors.length ? errors : undefined;
}

export const evaluateRules = async <ConsequencePayload, C extends Context,
    RF extends RuleFunctionsTable<C, ConsequencePayload, CustomEngineRuleFuncRunOptions>,
    F extends FunctionsTable<C, CustomEngineRuleFuncRunOptions>,
    Ignore = never, CustomEngineRuleFuncRunOptions = undefined>(rules: Rule<ConsequencePayload,
      RF, C, F, Ignore, CustomEngineRuleFuncRunOptions>[], context: C, functionsTable: F, ruleFunctionsTable: RF,
 haltOnFirstMatch: boolean, runOptions: CustomEngineRuleFuncRunOptions)
    : Promise<void | ResolvedConsequence<ConsequencePayload>[]> => {
    return run<ConsequencePayload, C, RF, F, Ignore, CustomEngineRuleFuncRunOptions>(
        rules, context, functionsTable, ruleFunctionsTable, haltOnFirstMatch, false, runOptions);
}

export const validateRules = async <ConsequencePayload, C extends Context,
    RF extends RuleFunctionsTable<C, ConsequencePayload, CustomEngineRuleFuncRunOptions>,
    F extends FunctionsTable<C, CustomEngineRuleFuncRunOptions>,
    Ignore = never, CustomEngineRuleFuncRunOptions = undefined>(
        rules: Rule<ConsequencePayload, RF, C, F, Ignore, CustomEngineRuleFuncRunOptions>[],
        validationContext: ValidationContext<C, Ignore>,
 functionsTable: F, ruleFunctionsTable: RF, runOptions: CustomEngineRuleFuncRunOptions)
    : Promise<void> => {
    await run<ConsequencePayload, C, RF, F, Ignore, CustomEngineRuleFuncRunOptions>(
        rules, validationContext, functionsTable,
        ruleFunctionsTable, false, true, runOptions);
}