Skip to content

Commit 61ac8d5

Browse files
author
Christopher Pardy
committed
Move Top Level enforcement to ConditionConstructor
Move the enforcement of top-level conditions in the rules and the engine to the top-level condition constructor this also allows for passing Condition instances to the setCondition method by default Ultimately this adds a huge amount of support for new types of conditions that can be plugged into the system.
1 parent 7bf1a2e commit 61ac8d5

File tree

9 files changed

+49
-26
lines changed

9 files changed

+49
-26
lines changed

docs/engine.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ 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)
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 TopLevelConditionConstructor())
5858

5959
### engine.addFact(String id, Function [definitionFunc], Object [options])
6060

docs/rules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ 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)
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 TopLevelConditionConstructor())
7777

7878
### setConditions(Array conditions)
7979

src/condition/condition-constructor.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,29 @@ export default class ConditionConstructor {
3030
return new ComparisonCondition(options)
3131
}
3232
}
33+
34+
export class TopLevelConditionConstructor extends ConditionConstructor {
35+
constructor (nestedConditionConstructor) {
36+
super()
37+
this.nestedConditionConstructor = nestedConditionConstructor || new ConditionConstructor()
38+
}
39+
40+
construct (options) {
41+
if (!options) {
42+
throw new Error('Condition: constructor options required')
43+
}
44+
if (options instanceof Condition) {
45+
return options
46+
}
47+
if (Object.prototype.hasOwnProperty.call(options, 'any')) {
48+
return new AnyCondition(options, this.nestedConditionConstructor)
49+
} else if (Object.prototype.hasOwnProperty.call(options, 'all')) {
50+
return new AllCondition(options, this.nestedConditionConstructor)
51+
} else if (Object.prototype.hasOwnProperty.call(options, 'not')) {
52+
return new NotCondition(options, this.nestedConditionConstructor)
53+
} else if (Object.prototype.hasOwnProperty.call(options, 'condition')) {
54+
return new ConditionReference(options)
55+
}
56+
throw new Error('"conditions" root must contain a single instance of "all", "any", "not", or "condition"')
57+
}
58+
}

src/condition/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import AllCondition from './all-condition'
22
import AnyCondition from './any-condition'
3-
import ConditionConstructor from './condition-constructor'
3+
import ConditionConstructor, { TopLevelConditionConstructor } from './condition-constructor'
44
import neverCondition from './never-condition'
55
import ComparisonCondition from './comparison-condition'
66
import ConditionReference from './condition-reference'
@@ -15,5 +15,6 @@ export {
1515
neverCondition,
1616
ComparisonCondition,
1717
ConditionReference,
18-
NotCondition
18+
NotCondition,
19+
TopLevelConditionConstructor
1920
}

src/engine.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Almanac from './almanac'
77
import EventEmitter from 'eventemitter2'
88
import defaultOperators from './engine-default-operators'
99
import debug from './debug'
10-
import ConditionConstructor from './condition'
10+
import { TopLevelConditionConstructor } from './condition'
1111

1212
export const READY = 'READY'
1313
export const RUNNING = 'RUNNING'
@@ -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 = options.conditionConstructor || new ConditionConstructor()
28+
this.conditionConstructor = options.conditionConstructor || new TopLevelConditionConstructor()
2929
this.operators = new Map()
3030
this.facts = new Map()
3131
this.conditions = new Map()
@@ -109,9 +109,6 @@ class Engine extends EventEmitter {
109109
setCondition (name, conditions) {
110110
if (!name) throw new Error('Engine: setCondition() requires name')
111111
if (!conditions) throw new Error('Engine: setCondition() requires conditions')
112-
if (!Object.prototype.hasOwnProperty.call(conditions, 'all') && !Object.prototype.hasOwnProperty.call(conditions, 'any') && !Object.prototype.hasOwnProperty.call(conditions, 'not') && !Object.prototype.hasOwnProperty.call(conditions, 'condition')) {
113-
throw new Error('"conditions" root must contain a single instance of "all", "any", "not", or "condition"')
114-
}
115112
this.conditions.set(name, this.conditionConstructor.construct(conditions))
116113
return this
117114
}

src/json-rules-engine.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +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'
5+
import ConditionConstructor, { Condition, TopLevelConditionConstructor } from './condition'
66

7-
export { Fact, Rule, Operator, Engine, ConditionConstructor, Condition }
7+
export { Fact, Rule, Operator, Engine, ConditionConstructor, Condition, TopLevelConditionConstructor }
88
export default function (rules, options) {
99
return new Engine(rules, options)
1010
}

src/rule.js

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import RuleResult from './rule-result'
44
import EventEmitter from 'eventemitter2'
5-
import ConditionConstructor, { neverCondition } from './condition'
5+
import { neverCondition, TopLevelConditionConstructor } from './condition'
66

77
class Rule extends EventEmitter {
88
/**
@@ -24,7 +24,7 @@ class Rule extends EventEmitter {
2424
if (options && options.conditionConstructor) {
2525
this.conditionConstructor = options.conditionConstructor
2626
} else {
27-
this.conditionConstructor = new ConditionConstructor()
27+
this.conditionConstructor = new TopLevelConditionConstructor()
2828
}
2929
if (options && options.conditions) {
3030
this.setConditions(options.conditions)
@@ -74,16 +74,6 @@ class Rule extends EventEmitter {
7474
* @param {object} conditions - conditions, root element must be a boolean operator
7575
*/
7676
setConditions (conditions) {
77-
if (
78-
!Object.prototype.hasOwnProperty.call(conditions, 'all') &&
79-
!Object.prototype.hasOwnProperty.call(conditions, 'any') &&
80-
!Object.prototype.hasOwnProperty.call(conditions, 'not') &&
81-
!Object.prototype.hasOwnProperty.call(conditions, 'condition')
82-
) {
83-
throw new Error(
84-
'"conditions" root must contain a single instance of "all", "any", "not", or "condition"'
85-
)
86-
}
8777
this.conditions = this.conditionConstructor.construct(conditions)
8878
return this
8979
}

test/engine-condition-constructor.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,9 @@ describe('Engine: conditionConstructor', () => {
3333
engine.addRule(ruleOpts)
3434
expect(conditionConstructor.construct).to.have.been.calledWith(ruleOpts.conditions)
3535
})
36+
37+
it('allows non-top-level conditions if the constructor will allow them.', () => {
38+
const conditionOpts = { never: true }
39+
expect(engine.setCondition.bind(engine, 'test', conditionOpts)).not.to.throw()
40+
})
3641
})

types/index.d.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class Engine {
2626
removeRule(ruleOrName: Rule | string): boolean;
2727
updateRule(rule: Rule): void;
2828

29-
setCondition(name: string, conditions: TopLevelCondition): this;
29+
setCondition(name: string, conditions: object | Condition): this;
3030
removeCondition(name: string): boolean;
3131

3232
addOperator(operator: Operator): Map<string, Operator>;
@@ -137,10 +137,10 @@ export interface RuleResult {
137137
export class Rule implements RuleProperties {
138138
constructor(ruleProps: RuleProperties | string);
139139
name: string;
140-
conditions: TopLevelCondition;
140+
conditions: Condition;
141141
event: Event;
142142
priority: number;
143-
setConditions(conditions: TopLevelCondition): this;
143+
setConditions(conditions: object | Condition): this;
144144
setEvent(event: Event): this;
145145
setPriority(priority: number): this;
146146
toJSON(): string;
@@ -165,6 +165,10 @@ export class ConditionConstructor {
165165
construct(options: object): Condition
166166
}
167167

168+
export class TopLevelConditionConstructor extends ConditionConstructor {
169+
constructor(nestedConditionConstructor?: ConditionConstructor)
170+
}
171+
168172
interface ConditionProperties {
169173
fact: string;
170174
operator: string;

0 commit comments

Comments
 (0)