diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74485e1..0d3e713 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: - '**' env: - PRIMARY_NODE_VERSION: 16.x + PRIMARY_NODE_VERSION: 18.x PRIMARY_OS: ubuntu-latest REGISTRY: https://registry.npmjs.org/ diff --git a/README.md b/README.md index ad93abc..a0dc5a4 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ yarn add json-expression-eval *Please see tests and examples dir for more usages and examples (under /src)* ```typescript -import {evaluate, Expression, ExpressionHandler, validate, ValidationContext} from 'json-expression-eval'; +import {evaluate, Expression, ExpressionHandler, validate, ValidationContext, EvaluatorFuncRunOptions} from 'json-expression-eval'; import {Moment} from 'moment'; import moment = require('moment'); @@ -82,7 +82,7 @@ const validationContext: ValidationContext => { + countRange: async ([min, max]: [min: number, max: number], ctx: { times: number | undefined }, runOptions: EvaluatorFuncRunOptions): Promise => { return ctx.times === undefined ? false : ctx.times >= min && ctx.times < max; }, }; @@ -140,7 +140,7 @@ There are 4 types of operators you can use (evaluated in that order of precedenc - `and` - accepts a non-empty list of expressions - `or` - accepts a non-empty list of expressions - `not` - accepts another expressions -- `` - accepts any type of argument and evaluated by the user defined functions, and the given context (can be async). +- `` - accepts any type of argument and evaluated by the user defined functions, and the given context (can be async) and run options (i.e. validation). - `` - operates on one of the context properties and compares it to a given value. - `{property: {op: value}}` - available ops: @@ -217,7 +217,7 @@ Example expressions, assuming we have the `user` and `maxCount` user defined fun *Please see tests and examples dir for more usages and examples (under /src)* ```typescript -import {ValidationContext, validateRules, evaluateRules, RulesEngine, Rule, ResolvedConsequence} from 'json-expression-eval'; +import {ValidationContext, validateRules, evaluateRules, RulesEngine, Rule, ResolvedConsequence, EngineRuleFuncRunOptions, EvaluatorFuncRunOptions} from 'json-expression-eval'; import {Moment} from 'moment'; import moment = require('moment'); @@ -238,11 +238,11 @@ type IExampleContextIgnore = Moment; type IExamplePayload = number; type IExampleFunctionTable = { - countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }) => boolean; + countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }, runOptions: EvaluatorFuncRunOptions) => boolean; } type IExampleRuleFunctionTable = { - userRule: (user: string, ctx: IExampleContext) => Promise>; + userRule: (user: string, ctx: IExampleContext, runOptions: EngineRuleFuncRunOptions) => Promise>; } type IExampleRule = Rule { + countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }, runOptions: EvaluatorFuncRunOptions): boolean => { return ctx.times === undefined ? false : ctx.times >= min && ctx.times < max; }, }; const ruleFunctionsTable: IExampleRuleFunctionTable = { - userRule: async (user: string, ctx: IExampleContext): Promise> => { + userRule: async (user: string, ctx: IExampleContext, runOptions: EngineRuleFuncRunOptions): Promise> => { if (ctx.userId === user) { return { message: `Username ${user} is not allowed`, diff --git a/package.json b/package.json index 6aba269..5807286 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-expression-eval", - "version": "5.1.2", + "version": "6.0.0", "description": "json serializable rule engine / boolean expression evaluator", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/lib/engine.ts b/src/lib/engine.ts index e828514..bf8025f 100644 --- a/src/lib/engine.ts +++ b/src/lib/engine.ts @@ -25,7 +25,7 @@ async function run(rule, ruleFunctionsTable, key)) { - const consequence = await ruleFunctionsTable[key](rule[key], context as C); + const consequence = await ruleFunctionsTable[key](rule[key], context as C, {validation}); if (consequence) { errors.push(consequence); if (haltOnFirstMatch && !validation) { diff --git a/src/lib/evaluator.ts b/src/lib/evaluator.ts index 7b99e35..65dcd56 100644 --- a/src/lib/evaluator.ts +++ b/src/lib/evaluator.ts @@ -189,7 +189,7 @@ async function run, Ignore> } else if (isNotCompareOp(expression)) { return !(await run(expression.not, context, functionsTable, validation)); } else if (isFunctionCompareOp(expression, functionsTable, expressionKey)) { - return validation ? true : await functionsTable[expressionKey](expression[expressionKey], context); + return functionsTable[expressionKey](expression[expressionKey], context, {validation}); } else { const {value: contextValue, exists} = getFromPath(context, expressionKey); if (validation && !exists) { diff --git a/src/test/evaluator.spec.ts b/src/test/evaluator.spec.ts index 3ee2d90..47b1693 100644 --- a/src/test/evaluator.spec.ts +++ b/src/test/evaluator.spec.ts @@ -1307,7 +1307,8 @@ describe('evaluator', () => { ], }; await validate(expression, context, fnTable); - expect(fnCounter).to.eql(0); + expect(fnCounter).to.eql(4); + fnCounter = 0; await evaluate(expression, context, fnTable); expect(fnCounter).to.eql(3); }); @@ -1338,7 +1339,8 @@ describe('evaluator', () => { }; const context = {}; await validate(expression, context, fnTable); - expect(fnCounter).to.eql(0); + expect(fnCounter).to.eql(4); + fnCounter = 0; await evaluate(expression, context, fnTable); expect(fnCounter).to.eql(3); }); diff --git a/src/types/engine.ts b/src/types/engine.ts index 16d16ef..8e215bc 100644 --- a/src/types/engine.ts +++ b/src/types/engine.ts @@ -29,8 +29,12 @@ export interface RuleDefinition; } +export type EngineRuleFuncRunOptions = { + validation: boolean; +} + export type RuleFunc = ( - param: any, context: C) => void | ResolvedConsequence + param: any, context: C, runOptions: EngineRuleFuncRunOptions) => void | ResolvedConsequence | Promise<(void | ResolvedConsequence)>; export type RuleFunctionsTable = Record>; diff --git a/src/types/evaluator.ts b/src/types/evaluator.ts index 8b7c0da..a18e370 100644 --- a/src/types/evaluator.ts +++ b/src/types/evaluator.ts @@ -128,7 +128,10 @@ export type FullExpression, Ignor export type Expression, Ignore = never> = RequireOnlyOne>; -export type Func = (param: any, context: T) => boolean | Promise; +export type EvaluatorFuncRunOptions = { + validation: boolean; +} +export type Func = (param: any, context: T, runOptions: EvaluatorFuncRunOptions) => boolean | Promise; export type FunctionsTable = Record>;