Skip to content

Commit b3e6e1a

Browse files
authored
add better validation logic (#27)
1 parent 49a26b8 commit b3e6e1a

8 files changed

Lines changed: 25 additions & 16 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ on:
1111
- '**'
1212

1313
env:
14-
PRIMARY_NODE_VERSION: 16.x
14+
PRIMARY_NODE_VERSION: 18.x
1515
PRIMARY_OS: ubuntu-latest
1616
REGISTRY: https://registry.npmjs.org/
1717

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ yarn add json-expression-eval
2727
*Please see tests and examples dir for more usages and examples (under /src)*
2828

2929
```typescript
30-
import {evaluate, Expression, ExpressionHandler, validate, ValidationContext} from 'json-expression-eval';
30+
import {evaluate, Expression, ExpressionHandler, validate, ValidationContext, EvaluatorFuncRunOptions} from 'json-expression-eval';
3131
import {Moment} from 'moment';
3232
import moment = require('moment');
3333

@@ -82,7 +82,7 @@ const validationContext: ValidationContext<IExampleContext, IExampleContextIgnor
8282
};
8383

8484
const functionsTable: IExampleFunctionTable = {
85-
countRange: async ([min, max]: [min: number, max: number], ctx: { times: number | undefined }): Promise<boolean> => {
85+
countRange: async ([min, max]: [min: number, max: number], ctx: { times: number | undefined }, runOptions: EvaluatorFuncRunOptions): Promise<boolean> => {
8686
return ctx.times === undefined ? false : ctx.times >= min && ctx.times < max;
8787
},
8888
};
@@ -140,7 +140,7 @@ There are 4 types of operators you can use (evaluated in that order of precedenc
140140
- `and` - accepts a non-empty list of expressions
141141
- `or` - accepts a non-empty list of expressions
142142
- `not` - accepts another expressions
143-
- `<user defined funcs>` - accepts any type of argument and evaluated by the user defined functions, and the given context (can be async).
143+
- `<user defined funcs>` - 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).
144144
- `<compare funcs>` - operates on one of the context properties and compares it to a given value.
145145
- `{property: {op: value}}`
146146
- available ops:
@@ -217,7 +217,7 @@ Example expressions, assuming we have the `user` and `maxCount` user defined fun
217217
*Please see tests and examples dir for more usages and examples (under /src)*
218218

219219
```typescript
220-
import {ValidationContext, validateRules, evaluateRules, RulesEngine, Rule, ResolvedConsequence} from 'json-expression-eval';
220+
import {ValidationContext, validateRules, evaluateRules, RulesEngine, Rule, ResolvedConsequence, EngineRuleFuncRunOptions, EvaluatorFuncRunOptions} from 'json-expression-eval';
221221
import {Moment} from 'moment';
222222
import moment = require('moment');
223223

@@ -238,11 +238,11 @@ type IExampleContextIgnore = Moment;
238238
type IExamplePayload = number;
239239

240240
type IExampleFunctionTable = {
241-
countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }) => boolean;
241+
countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }, runOptions: EvaluatorFuncRunOptions) => boolean;
242242
}
243243

244244
type IExampleRuleFunctionTable = {
245-
userRule: (user: string, ctx: IExampleContext) => Promise<void | ResolvedConsequence<IExamplePayload>>;
245+
userRule: (user: string, ctx: IExampleContext, runOptions: EngineRuleFuncRunOptions) => Promise<void | ResolvedConsequence<IExamplePayload>>;
246246
}
247247

248248
type IExampleRule = Rule<IExamplePayload, IExampleRuleFunctionTable, IExampleContext,
@@ -275,13 +275,13 @@ const validationContext: ValidationContext<IExampleContext, IExampleContextIgnor
275275
};
276276

277277
const functionsTable: IExampleFunctionTable = {
278-
countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }): boolean => {
278+
countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }, runOptions: EvaluatorFuncRunOptions): boolean => {
279279
return ctx.times === undefined ? false : ctx.times >= min && ctx.times < max;
280280
},
281281
};
282282

283283
const ruleFunctionsTable: IExampleRuleFunctionTable = {
284-
userRule: async (user: string, ctx: IExampleContext): Promise<void | ResolvedConsequence<number>> => {
284+
userRule: async (user: string, ctx: IExampleContext, runOptions: EngineRuleFuncRunOptions): Promise<void | ResolvedConsequence<number>> => {
285285
if (ctx.userId === user) {
286286
return {
287287
message: `Username ${user} is not allowed`,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "json-expression-eval",
3-
"version": "5.1.2",
3+
"version": "6.0.0",
44
"description": "json serializable rule engine / boolean expression evaluator",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/lib/engine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ async function run<ConsequencePayload, C extends Context,
2525
const keys = objectKeys(rule);
2626
const key = keys[0];
2727
if (keys.length === 1 && key && isRuleFunction<ConsequencePayload, C, RF>(rule, ruleFunctionsTable, key)) {
28-
const consequence = await ruleFunctionsTable[key](rule[key], context as C);
28+
const consequence = await ruleFunctionsTable[key](rule[key], context as C, {validation});
2929
if (consequence) {
3030
errors.push(consequence);
3131
if (haltOnFirstMatch && !validation) {

src/lib/evaluator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ async function run<C extends Context, F extends FunctionsTable<C>, Ignore>
189189
} else if (isNotCompareOp<C, F, Ignore>(expression)) {
190190
return !(await run<C, F, Ignore>(expression.not, context, functionsTable, validation));
191191
} else if (isFunctionCompareOp<C, F, Ignore>(expression, functionsTable, expressionKey)) {
192-
return validation ? true : await functionsTable[expressionKey](expression[expressionKey], context);
192+
return functionsTable[expressionKey](expression[expressionKey], context, {validation});
193193
} else {
194194
const {value: contextValue, exists} = getFromPath(context, expressionKey);
195195
if (validation && !exists) {

src/test/evaluator.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,7 +1307,8 @@ describe('evaluator', () => {
13071307
],
13081308
};
13091309
await validate(expression, context, fnTable);
1310-
expect(fnCounter).to.eql(0);
1310+
expect(fnCounter).to.eql(4);
1311+
fnCounter = 0;
13111312
await evaluate(expression, context, fnTable);
13121313
expect(fnCounter).to.eql(3);
13131314
});
@@ -1338,7 +1339,8 @@ describe('evaluator', () => {
13381339
};
13391340
const context = {};
13401341
await validate<typeof context, typeof fnTable>(expression, context, fnTable);
1341-
expect(fnCounter).to.eql(0);
1342+
expect(fnCounter).to.eql(4);
1343+
fnCounter = 0;
13421344
await evaluate(expression, context, fnTable);
13431345
expect(fnCounter).to.eql(3);
13441346
});

src/types/engine.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@ export interface RuleDefinition<ConsequencePayload, C extends Context, F extends
2929
consequence: RuleConsequence<ConsequencePayload, C, Ignore>;
3030
}
3131

32+
export type EngineRuleFuncRunOptions = {
33+
validation: boolean;
34+
}
35+
3236
export type RuleFunc<C, ConsequencePayload> = (
33-
param: any, context: C) => void | ResolvedConsequence<ConsequencePayload>
37+
param: any, context: C, runOptions: EngineRuleFuncRunOptions) => void | ResolvedConsequence<ConsequencePayload>
3438
| Promise<(void | ResolvedConsequence<ConsequencePayload>)>;
3539

3640
export type RuleFunctionsTable<C, ConsequencePayload> = Record<string, RuleFunc<C, ConsequencePayload>>;

src/types/evaluator.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,10 @@ export type FullExpression<C extends Context, F extends FunctionsTable<C>, Ignor
128128
export type Expression<C extends Context, F extends FunctionsTable<C>, Ignore = never> =
129129
RequireOnlyOne<FullExpression<C, F, Ignore>>;
130130

131-
export type Func<T> = (param: any, context: T) => boolean | Promise<boolean>;
131+
export type EvaluatorFuncRunOptions = {
132+
validation: boolean;
133+
}
134+
export type Func<T> = (param: any, context: T, runOptions: EvaluatorFuncRunOptions) => boolean | Promise<boolean>;
132135

133136
export type FunctionsTable<T> = Record<string, Func<T>>;
134137

0 commit comments

Comments
 (0)