Skip to content

Commit c240ef8

Browse files
author
Adam Jenkins
committed
Do the work
1 parent 5052ac6 commit c240ef8

File tree

6 files changed

+54
-20
lines changed

6 files changed

+54
-20
lines changed

Diff for: src/almanac.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@ import Fact from './fact'
44
import { UndefinedFactError } from './errors'
55
import debug from './debug'
66

7-
import { JSONPath } from 'jsonpath-plus'
87
import isObjectLike from 'lodash.isobjectlike'
9-
10-
function defaultPathResolver (value, path) {
11-
return JSONPath({ path, json: value, wrap: false })
12-
}
8+
import { defaultPathResolver } from './resolver';
139

1410
/**
1511
* Fact results lookup

Diff for: src/engine.js

+38-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import Almanac from './almanac'
77
import EventEmitter from 'eventemitter2'
88
import defaultOperators from './engine-default-operators'
99
import debug from './debug'
10+
import { interpolateDeep, needsInterpolation, defaultInterpolation } from './interpolate';
11+
import { defaultPathResolver } from './resolver';
1012

1113
export const READY = 'READY'
1214
export const RUNNING = 'RUNNING'
@@ -21,12 +23,16 @@ class Engine extends EventEmitter {
2123
super()
2224
this.rules = []
2325
this.allowUndefinedFacts = options.allowUndefinedFacts || false
24-
this.pathResolver = options.pathResolver
26+
this.pathResolver = options.pathResolver || defaultPathResolver
27+
const interpolation = options.interpolation || defaultInterpolation;
28+
this.interpolation = typeof interpolation === 'string' ? new RegExp(interpolation) : interpolation;
2529
this.operators = new Map()
2630
this.facts = new Map()
2731
this.status = READY
2832
rules.map(r => this.addRule(r))
2933
defaultOperators.map(o => this.addOperator(o))
34+
35+
this.interpolatableRules = new Set();
3036
}
3137

3238
/**
@@ -49,6 +55,7 @@ class Engine extends EventEmitter {
4955
if (!Object.prototype.hasOwnProperty.call(properties, 'conditions')) throw new Error('Engine: addRule() argument requires "conditions" property')
5056
rule = new Rule(properties)
5157
}
58+
if(needsInterpolation(rule,this.interpolation)) this.interpolatableRules.add(rule);
5259
rule.setEngine(this)
5360
this.rules.push(rule)
5461
this.prioritizedRules = null
@@ -62,7 +69,8 @@ class Engine extends EventEmitter {
6269
updateRule (rule) {
6370
const ruleIndex = this.rules.findIndex(ruleInEngine => ruleInEngine.name === rule.name)
6471
if (ruleIndex > -1) {
65-
this.rules.splice(ruleIndex, 1)
72+
const [old] = this.rules.splice(ruleIndex, 1)
73+
this.interpolatableRules.delete(old);
6674
this.addRule(rule)
6775
this.prioritizedRules = null
6876
} else {
@@ -75,21 +83,25 @@ class Engine extends EventEmitter {
7583
* @param {object|Rule|string} rule - rule definition. Must be a instance of Rule
7684
*/
7785
removeRule (rule) {
78-
let ruleRemoved = false
86+
let theRemovedRule;
7987
if (!(rule instanceof Rule)) {
80-
const filteredRules = this.rules.filter(ruleInEngine => ruleInEngine.name !== rule)
81-
ruleRemoved = filteredRules.length !== this.rules.length
88+
const filteredRules = this.rules.filter(ruleInEngine => {
89+
const isRule = ruleInEngine.name === rule;
90+
if(!theRemovedRule && isRule) theRemovedRule = ruleInEngine;
91+
return !isRule;
92+
});
8293
this.rules = filteredRules
8394
} else {
8495
const index = this.rules.indexOf(rule)
8596
if (index > -1) {
86-
ruleRemoved = Boolean(this.rules.splice(index, 1).length)
97+
theRemovedRule = this.rules.splice(index, 1)[0];
8798
}
8899
}
89-
if (ruleRemoved) {
100+
if (theRemovedRule) {
90101
this.prioritizedRules = null
102+
this.interpolatableRules.delete(theRemovedRule);
91103
}
92-
return ruleRemoved
104+
return Boolean(theRemovedRule);
93105
}
94106

95107
/**
@@ -211,6 +223,7 @@ class Engine extends EventEmitter {
211223
debug(`engine::run status:${this.status}; skipping remaining rules`)
212224
return Promise.resolve()
213225
}
226+
214227
return rule.evaluate(almanac).then((ruleResult) => {
215228
debug(`engine::run ruleResult:${ruleResult.result}`)
216229
almanac.addResult(ruleResult)
@@ -240,12 +253,28 @@ class Engine extends EventEmitter {
240253
pathResolver: this.pathResolver
241254
}
242255
const almanac = new Almanac(this.facts, runtimeFacts, almanacOptions)
243-
const orderedSets = this.prioritizeRules()
256+
const orderedSets = this.prioritizeRules();
257+
244258
let cursor = Promise.resolve()
245259
// for each rule set, evaluate in parallel,
246260
// before proceeding to the next priority set.
247261
return new Promise((resolve, reject) => {
248262
orderedSets.map((set) => {
263+
set = set.map(rule => {
264+
if(!this.interpolatableRules.has(rule)) return rule;
265+
rule = new Rule(
266+
interpolateDeep(
267+
rule.toJSON(true),
268+
runtimeFacts,
269+
this.interpolation,
270+
this.pathResolver
271+
)
272+
);
273+
rule.setEngine(this);
274+
return rule;
275+
});
276+
277+
249278
cursor = cursor.then(() => {
250279
return this.evaluateRules(set, almanac)
251280
}).catch(reject)

Diff for: src/resolver.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { JSONPath } from 'jsonpath-plus'
2+
3+
export function defaultPathResolver (value, path) {
4+
return JSONPath({ path, json: value, wrap: false })
5+
}

Diff for: src/rule.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Condition from './condition'
44
import RuleResult from './rule-result'
55
import debug from './debug'
66
import EventEmitter from 'eventemitter2'
7+
import { needsInterpolation } from './interpolate'
78

89
class Rule extends EventEmitter {
910
/**

Diff for: test/engine-fact.test.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,15 @@ describe('Engine: fact evaluation', () => {
6161
operator: 'lessThan',
6262
params: {
6363
eligibilityId: 1,
64-
field: 'age'
64+
field: '{{whichField}}'
6565
},
6666
value: 50
6767
}]
6868
}
6969
}
70+
const runtimeFacts = {
71+
whichField:'age'
72+
}
7073
let successSpy
7174
let failureSpy
7275
beforeEach(() => {
@@ -94,7 +97,7 @@ describe('Engine: fact evaluation', () => {
9497
value: true
9598
})
9699
setup(conditions)
97-
return expect(engine.run()).to.be.rejectedWith(/Undefined fact: undefined-fact/)
100+
return expect(engine.run(runtimeFacts)).to.be.rejectedWith(/Undefined fact: undefined-fact/)
98101
})
99102

100103
context('treats undefined facts as falsey when allowUndefinedFacts is set', () => {
@@ -106,7 +109,7 @@ describe('Engine: fact evaluation', () => {
106109
value: true
107110
})
108111
setup(conditions, { allowUndefinedFacts: true })
109-
await engine.run()
112+
await engine.run(runtimeFacts)
110113
expect(successSpy).to.have.been.called()
111114
expect(failureSpy).to.not.have.been.called()
112115
})
@@ -131,7 +134,7 @@ describe('Engine: fact evaluation', () => {
131134
describe('params', () => {
132135
it('emits when the condition is met', async () => {
133136
setup()
134-
await engine.run()
137+
await engine.run(runtimeFacts)
135138
expect(successSpy).to.have.been.calledWith(event)
136139
})
137140

Diff for: test/engine.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sinon from 'sinon'
44
import engineFactory, { Fact, Rule, Operator } from '../src/index'
55
import defaultOperators from '../src/engine-default-operators'
6+
import Condition from '../src/condition'
67

78
describe('Engine', () => {
89
const operatorCount = defaultOperators.length
@@ -92,8 +93,7 @@ describe('Engine', () => {
9293
engine.addRule(rule2)
9394
expect(engine.rules[0].conditions.all.length).to.equal(2)
9495
expect(engine.rules[1].conditions.all.length).to.equal(2)
95-
96-
rule1.conditions = { all: [] }
96+
rule1.conditions = new Condition({all:[]})
9797
engine.updateRule(rule1)
9898

9999
rule1 = engine.rules.find(rule => rule.name === 'rule1')

0 commit comments

Comments
 (0)