Skip to content

Commit fd1a25f

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 20bc733 commit fd1a25f

File tree

9 files changed

+48
-25
lines changed

9 files changed

+48
-25
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ 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

77
export { Fact, Rule, Operator, Engine, ConditionConstructor, Condition }
88
export default function (rules, options) {

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
@@ -34,4 +34,9 @@ describe('Engine: conditionConstructor', () => {
3434
expect(conditionConstructor.construct).to.have.been.calledWith(ruleOpts.conditions)
3535
})
3636

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+
})
41+
3742
})

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)