Skip to content

Commit 20bc733

Browse files
author
Christopher Pardy
committed
Support Passing in a condition constructor
Support passing a condition constructor into the engine and the rules in order to allow for custom conditions.
1 parent b965b59 commit 20bc733

File tree

7 files changed

+70
-3
lines changed

7 files changed

+70
-3
lines changed

docs/engine.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ as failed conditions. (default: false)
5454

5555
`pathResolver` - Allows a custom object path resolution library to be used. (default: `json-path` syntax). See [custom path resolver](./rules.md#condition-helpers-custom-path-resolver) docs.
5656

57+
`conditionConstructor` - The condition constructor class to use when given condition options. Allows for custom conditions to be created by specifying a new condition constructor. (default: new ConditionConstructor)
58+
5759
### engine.addFact(String id, Function [definitionFunc], Object [options])
5860

5961
```js

docs/rules.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ let rule = new Rule(options)
7373

7474
**options.name** : `[Any]` A way of naming your rules, allowing them to be easily identifiable in [Rule Results](#rule-results). This is usually of type `String`, but could also be `Object`, `Array`, or `Number`. Note that the name need not be unique, and that it has no impact on execution of the rule.
7575

76+
**options.conditionConstructor**: [ConditionConstructor] - The condition constructor class to use when given condition options. Allows for custom conditions to be created by specifying a new condition constructor. (default: new ConditionConstructor)
77+
7678
### setConditions(Array conditions)
7779

7880
Helper for setting rule conditions. Alternative to passing the `conditions` option to the rule constructor.

src/engine.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class Engine extends EventEmitter {
2525
this.allowUndefinedConditions = options.allowUndefinedConditions || false
2626
this.replaceFactsInEventParams = options.replaceFactsInEventParams || false
2727
this.pathResolver = options.pathResolver
28-
this.conditionConstructor = new ConditionConstructor();
28+
this.conditionConstructor = options.conditionConstructor || new ConditionConstructor();
2929
this.operators = new Map()
3030
this.facts = new Map()
3131
this.conditions = new Map()
@@ -52,6 +52,9 @@ class Engine extends EventEmitter {
5252
} else {
5353
if (!Object.prototype.hasOwnProperty.call(properties, 'event')) throw new Error('Engine: addRule() argument requires "event" property')
5454
if (!Object.prototype.hasOwnProperty.call(properties, 'conditions')) throw new Error('Engine: addRule() argument requires "conditions" property')
55+
if (!Object.prototype.hasOwnProperty.call(properties, 'conditionConstructor')) {
56+
properties.conditionConstructor = this.conditionConstructor
57+
}
5558
rule = new Rule(properties)
5659
}
5760
rule.setEngine(this)

src/json-rules-engine.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import Engine from './engine'
22
import Fact from './fact'
33
import Rule from './rule'
44
import Operator from './operator'
5+
import ConditionConstructor, { Condition } from './condition'
56

6-
export { Fact, Rule, Operator, Engine }
7+
export { Fact, Rule, Operator, Engine, ConditionConstructor, Condition }
78
export default function (rules, options) {
89
return new Engine(rules, options)
910
}

src/rule.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ class Rule extends EventEmitter {
2121
if (typeof options === "string") {
2222
options = JSON.parse(options);
2323
}
24-
this.conditionConstructor = new ConditionConstructor();
24+
if (options && options.conditionConstructor) {
25+
this.conditionConstructor = options.conditionConstructor
26+
} else {
27+
this.conditionConstructor = new ConditionConstructor()
28+
}
2529
if (options && options.conditions) {
2630
this.setConditions(options.conditions);
2731
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict'
2+
3+
import sinon from 'sinon'
4+
import engineFactory, { ConditionConstructor, Condition } from '../src/index'
5+
6+
describe('Engine: conditionConstructor', () => {
7+
let engine
8+
let conditionConstructor
9+
let sandbox
10+
before(() => {
11+
sandbox = sinon.createSandbox()
12+
})
13+
afterEach(() => {
14+
sandbox.restore()
15+
})
16+
17+
beforeEach(() => {
18+
conditionConstructor = { construct: sandbox.spy() }
19+
engine = engineFactory([], { conditionConstructor })
20+
})
21+
22+
it('invokes the condition constructor when adding a condition', () => {
23+
const conditionOpts = { all: []}
24+
engine.setCondition('test', conditionOpts)
25+
expect(conditionConstructor.construct).to.have.been.calledWith(conditionOpts)
26+
})
27+
28+
it('invokes the condition constructor when adding a rule', () => {
29+
const ruleOpts = {
30+
conditions: { all: [] },
31+
event: { type: 'unknown' }
32+
}
33+
engine.addRule(ruleOpts);
34+
expect(conditionConstructor.construct).to.have.been.calledWith(ruleOpts.conditions)
35+
})
36+
37+
})

types/index.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export interface EngineOptions {
33
allowUndefinedConditions?: boolean;
44
replaceFactsInEventParams?: boolean;
55
pathResolver?: PathResolver;
6+
conditionConstructor?: ConditionConstructor;
67
}
78

89
export interface EngineResult {
@@ -118,6 +119,7 @@ export interface RuleProperties {
118119
priority?: number;
119120
onSuccess?: EventHandler;
120121
onFailure?: EventHandler;
122+
conditionConstructor?: ConditionConstructor;
121123
}
122124
export type RuleSerializable = Pick<
123125
Required<RuleProperties>,
@@ -147,6 +149,22 @@ export class Rule implements RuleProperties {
147149
): T extends true ? string : RuleSerializable;
148150
}
149151

152+
export class Condition {
153+
constructor(properties: object);
154+
toJSON(stringify: true): {name?: string, priority?: number}
155+
getPriority(almanac: Almanac): number | undefined;
156+
evaluate(
157+
almanac: Almanac,
158+
operatorMap: { get(operatorName: string): Operator },
159+
conditionMap: { get(conditionName): Condition}
160+
): Promise<{ result: boolean, priority: number | undefined, name?: string }>
161+
skip(): {name?: string, priority?: number}
162+
}
163+
164+
export class ConditionConstructor {
165+
construct(options: object): Condition
166+
}
167+
150168
interface ConditionProperties {
151169
fact: string;
152170
operator: string;

0 commit comments

Comments
 (0)