Skip to content

Commit

Permalink
added validation method
Browse files Browse the repository at this point in the history
  • Loading branch information
regevbr committed Mar 2, 2021
1 parent 861aff4 commit 12d6e80
Show file tree
Hide file tree
Showing 4 changed files with 366 additions and 139 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ yarn add json-expression-eval
*Please see tests and examples dir for more usages and example (under /src)*

```typescript
import { Expression, evaluate } from 'json-expression-eval';
import { Expression, evaluate, validate } from 'json-expression-eval';

interface IExampleContext {
userId: string;
Expand Down Expand Up @@ -62,6 +62,7 @@ const expression: Expression<IExampleContext, typeof functionsTable> = {
},
],
};
validate(expression, exampleContext, functionsTable); // Should not throw
console.log(evaluate(expression, exampleContext, functionsTable)); // true
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "json-expression-eval",
"version": "3.0.1",
"version": "3.1.0",
"description": "evaluate a json described boolean expression using dynamic functions",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
126 changes: 94 additions & 32 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,78 +116,140 @@ const _isObject = (obj: any) => {
return type === 'function' || type === 'object' && !!obj;
};

const evaluateCompareOp = <C, F extends FunctionsTable<C>, K extends keyof RequireOnlyOne<CompareOp<C, F>>>(
op: RequireOnlyOne<CompareOp<C, F>>, key: K, param: any): boolean => {
function evaluateCompareOp<C, F extends FunctionsTable<C>, K extends keyof RequireOnlyOne<CompareOp<C, F>>>(
op: RequireOnlyOne<CompareOp<C, F>>, key: K, param: any, validation: false): boolean;
function evaluateCompareOp<C, F extends FunctionsTable<C>, K extends keyof RequireOnlyOne<CompareOp<C, F>>>(
op: RequireOnlyOne<CompareOp<C, F>>, key: K, param: any, validation: true): void;
function evaluateCompareOp<C, F extends FunctionsTable<C>, K extends keyof RequireOnlyOne<CompareOp<C, F>>>(
op: RequireOnlyOne<CompareOp<C, F>>, key: K, param: any, validation: boolean): boolean | void {
const value = op[key];
if (!_isObject(value)) {
return param === value;
return validation ? undefined : param === value;
}
const keys = Object.keys(value);
if (keys.length !== 1) {
throw new Error('Invalid expression - too may keys');
}
const valueKey = keys[0];
if (isGtCompareOp(value)) {
return param > value.gt;
return validation ? undefined : param > value.gt;
} else if (isGteCompareOp(value)) {
return param >= value.gte;
return validation ? undefined : param >= value.gte;
} else if (isLteCompareOp(value)) {
return param <= value.lte;
return validation ? undefined : param <= value.lte;
} else if (isLtCompareOp(value)) {
return param < value.lt;
return validation ? undefined : param < value.lt;
} else if (isEqualCompareOp(value)) {
return param === value.eq;
return validation ? undefined : param === value.eq;
} else if (isNotEqualCompareOp(value)) {
return param !== value.neq;
return validation ? undefined : param !== value.neq;
} else if (_isObject(param) && valueKey in param) {
return evaluateCompareOp(value, valueKey, param[valueKey]);
if (validation) {
evaluateCompareOp(value, valueKey, param[valueKey], true);
return;
} else {
return evaluateCompareOp(value, valueKey, param[valueKey], false);
}
}
throw new Error(`Invalid expression - unknown op ${valueKey}`);
};
}

const handleAndOp = <C extends Context, F extends FunctionsTable<C>>(andExpression: Expression<C, F>[], context: C,
functionsTable: F): boolean => {
function handleAndOp<C extends Context, F extends FunctionsTable<C>>(andExpression: Expression<C, F>[], context: C,
functionsTable: F, validation: false): boolean;
function handleAndOp<C extends Context, F extends FunctionsTable<C>>(andExpression: Expression<C, F>[], context: C,
functionsTable: F, validation: true): void;
function handleAndOp<C extends Context, F extends FunctionsTable<C>>(andExpression: Expression<C, F>[], context: C,
functionsTable: F, validation: boolean)
: boolean | void {
if (andExpression.length === 0) {
throw new Error('Invalid expression - and operator must have at least one expression');
}
for (const currExpression of andExpression) {
if (!evaluate(currExpression, context, functionsTable)) {
return false
if (validation) {
andExpression.forEach((currExpression) => validate(currExpression, context, functionsTable));
} else {
for (const currExpression of andExpression) {
if (!evaluate(currExpression, context, functionsTable)) {
return false
}
}
return true;
}
return true;
};
}

const handleOrOp = <C extends Context, F extends FunctionsTable<C>>(orExpression: Expression<C, F>[], context: C,
functionsTable: F): boolean => {
function handleOrOp<C extends Context, F extends FunctionsTable<C>>(orExpression: Expression<C, F>[], context: C,
functionsTable: F, validation: false): boolean;
function handleOrOp<C extends Context, F extends FunctionsTable<C>>(orExpression: Expression<C, F>[], context: C,
functionsTable: F, validation: true): void;
function handleOrOp<C extends Context, F extends FunctionsTable<C>>(orExpression: Expression<C, F>[], context: C,
functionsTable: F, validation: boolean)
: boolean | void {
if (orExpression.length === 0) {
throw new Error('Invalid expression - or operator must have at least one expression');
}
for (const currExpression of orExpression) {
if (evaluate(currExpression, context, functionsTable)) {
return true
if (validation) {
orExpression.forEach((currExpression) => validate(currExpression, context, functionsTable));
} else {
for (const currExpression of orExpression) {
if (evaluate(currExpression, context, functionsTable)) {
return true
}
}
return false;
}
return false;
};
}

export const evaluate = <C extends Context, F extends FunctionsTable<C>>(expression: Expression<C, F>, context: C,
functionsTable: F): boolean => {
function _run<C extends Context, F extends FunctionsTable<C>>(expression: Expression<C, F>, context: C,
functionsTable: F, validation: false): boolean;
function _run<C extends Context, F extends FunctionsTable<C>>(expression: Expression<C, F>, context: C,
functionsTable: F, validation: true): void;
function _run<C extends Context, F extends FunctionsTable<C>>(expression: Expression<C, F>, context: C,
functionsTable: F, validation: boolean): boolean | void {
const keys = Object.keys(expression);
if (keys.length !== 1) {
throw new Error('Invalid expression - too may keys');
}
const key = keys[0];
if (isAndCompareOp<C, F>(expression)) {
return handleAndOp(expression.and, context, functionsTable);
if (validation) {
handleAndOp(expression.and, context, functionsTable, true);
return;
} else {
return handleAndOp(expression.and, context, functionsTable, false);
}
} else if (isOrCompareOp<C, F>(expression)) {
return handleOrOp(expression.or, context, functionsTable);
if (validation) {
handleOrOp(expression.or, context, functionsTable, true);
return;
} else {
return handleOrOp(expression.or, context, functionsTable, false);
}
} else if (isNotCompareOp<C, F>(expression)) {
return !evaluate(expression.not, context, functionsTable);
if (validation) {
_run(expression.not, context, functionsTable, true);
return;
} else {
return !_run(expression.not, context, functionsTable, false);
}
} else if (key in functionsTable) {
return functionsTable[key](expression[key], context);
return validation ? undefined : functionsTable[key](expression[key], context);
} else if (key in context) {
return evaluateCompareOp<C, F, typeof key>(expression, key, context[key]);
if (validation) {
evaluateCompareOp<C, F, typeof key>(expression, key, context[key], true)
return;
} else {
return evaluateCompareOp<C, F, typeof key>(expression, key, context[key], false)
}
}
throw new Error(`Invalid expression - unknown function ${key}`);
}

export const evaluate = <C extends Context, F extends FunctionsTable<C>>(expression: Expression<C, F>, context: C,
functionsTable: F): boolean => {
return _run(expression, context, functionsTable, false);
};

// Throws in case of validation error. Does not run functions or compare fields
export const validate = <C extends Context, F extends FunctionsTable<C>>(expression: Expression<C, F>, dummyContext: C,
functionsTable: F): void => {
_run(expression, dummyContext, functionsTable, true);
};
Loading

0 comments on commit 12d6e80

Please sign in to comment.