Skip to content

Commit 9c9edd7

Browse files
authored
Merge pull request #378 from chris-pardy/bug-fixes
Bug fixes
2 parents a74cb51 + 86d210d commit 9c9edd7

8 files changed

+38
-52
lines changed

examples/13-using-operator-decorators.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,11 @@ async function start () {
7979
}
8080
}
8181

82-
engine.addRule(caseInsensitiveValidTags);
82+
engine.addRule(caseInsensitiveValidTags)
8383

8484
// third run with a tag that is valid if case insensitive
8585
facts = { tags: ['dev', 'PROD'] }
86-
await engine.run(facts);
87-
86+
await engine.run(facts)
8887
}
8988
start()
9089

src/almanac.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export default class Almanac {
105105
} else {
106106
fact = new Fact(id, valueOrMethod, options)
107107
}
108-
debug(`almanac::addFact id:${factId}`)
108+
debug('almanac::addFact', { id: factId })
109109
this.factMap.set(factId, fact)
110110
if (fact.isConstant()) {
111111
this._setFactValue(fact, {}, fact.value)
@@ -120,7 +120,7 @@ export default class Almanac {
120120
* @param {Mixed} value - constant value of the fact
121121
*/
122122
addRuntimeFact (factId, value) {
123-
debug(`almanac::addRuntimeFact id:${factId}`)
123+
debug('almanac::addRuntimeFact', { id: factId })
124124
const fact = new Fact(factId, value)
125125
return this._addConstantFact(fact)
126126
}
@@ -150,22 +150,22 @@ export default class Almanac {
150150
const cacheVal = cacheKey && this.factResultsCache.get(cacheKey)
151151
if (cacheVal) {
152152
factValuePromise = Promise.resolve(cacheVal)
153-
debug(`almanac::factValue cache hit for fact:${factId}`)
153+
debug('almanac::factValue cache hit for fact', { id: factId })
154154
} else {
155-
debug(`almanac::factValue cache miss for fact:${factId}; calculating`)
155+
debug('almanac::factValue cache miss, calculating', { id: factId })
156156
factValuePromise = this._setFactValue(fact, params, fact.calculate(params, this))
157157
}
158158
}
159159
if (path) {
160-
debug(`condition::evaluate extracting object property ${path}`)
160+
debug('condition::evaluate extracting object', { property: path })
161161
return factValuePromise
162162
.then(factValue => {
163163
if (factValue != null && typeof factValue === 'object') {
164164
const pathValue = this.pathResolver(factValue, path)
165-
debug(`condition::evaluate extracting object property ${path}, received: ${JSON.stringify(pathValue)}`)
165+
debug('condition::evaluate extracting object', { property: path, received: pathValue })
166166
return pathValue
167167
} else {
168-
debug(`condition::evaluate could not compute object path(${path}) of non-object: ${factValue} <${typeof factValue}>; continuing with ${factValue}`)
168+
debug('condition::evaluate could not compute object path of non-object', { path, factValue, type: typeof factValue })
169169
return factValue
170170
}
171171
})

src/condition.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,12 @@ export default class Condition {
101101
]).then(([rightHandSideValue, leftHandSideValue]) => {
102102
const result = op.evaluate(leftHandSideValue, rightHandSideValue)
103103
debug(
104-
`condition::evaluate <${JSON.stringify(leftHandSideValue)} ${
105-
this.operator
106-
} ${JSON.stringify(rightHandSideValue)}?> (${result})`
104+
'condition::evaluate', {
105+
leftHandSideValue,
106+
operator: this.operator,
107+
rightHandSideValue,
108+
result
109+
}
107110
)
108111
return {
109112
result,

src/debug.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
export default function debug (message) {
1+
2+
function createDebug () {
23
try {
34
if ((typeof process !== 'undefined' && process.env && process.env.DEBUG && process.env.DEBUG.match(/json-rules-engine/)) ||
45
(typeof window !== 'undefined' && window.localStorage && window.localStorage.debug && window.localStorage.debug.match(/json-rules-engine/))) {
5-
console.log(message)
6+
return console.debug.bind(console)
67
}
78
} catch (ex) {
89
// Do nothing
910
}
11+
return () => {}
1012
}
13+
14+
export default createDebug()

src/engine.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ class Engine extends EventEmitter {
172172
} else {
173173
fact = new Fact(id, valueOrMethod, options)
174174
}
175-
debug(`engine::addFact id:${factId}`)
175+
debug('engine::addFact', { id: factId })
176176
this.facts.set(factId, fact)
177177
return this
178178
}
@@ -241,11 +241,11 @@ class Engine extends EventEmitter {
241241
evaluateRules (ruleArray, almanac) {
242242
return Promise.all(ruleArray.map((rule) => {
243243
if (this.status !== RUNNING) {
244-
debug(`engine::run status:${this.status}; skipping remaining rules`)
244+
debug('engine::run, skipping remaining rules', { status: this.status })
245245
return Promise.resolve()
246246
}
247247
return rule.evaluate(almanac).then((ruleResult) => {
248-
debug(`engine::run ruleResult:${ruleResult.result}`)
248+
debug('engine::run', { ruleResult: ruleResult.result })
249249
almanac.addResult(ruleResult)
250250
if (ruleResult.result) {
251251
almanac.addEvent(ruleResult.event, 'success')
@@ -286,7 +286,7 @@ class Engine extends EventEmitter {
286286
}
287287

288288
almanac.addFact(fact)
289-
debug(`engine::run initialized runtime fact:${fact.id} with ${fact.value}<${typeof fact.value}>`)
289+
debug('engine::run initialized runtime fact', { id: fact.id, value: fact.value, type: typeof fact.value })
290290
}
291291
const orderedSets = this.prioritizeRules()
292292
let cursor = Promise.resolve()

src/operator-map.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default class OperatorMap {
2222
} else {
2323
operator = new Operator(operatorOrName, cb)
2424
}
25-
debug(`operatorMap::addOperator name:${operator.name}`)
25+
debug('operatorMap::addOperator', { name: operator.name })
2626
this.operators.set(operator.name, operator)
2727
}
2828

@@ -64,7 +64,7 @@ export default class OperatorMap {
6464
} else {
6565
decorator = new OperatorDecorator(decoratorOrName, cb)
6666
}
67-
debug(`operatorMap::addOperatorDecorator name:${decorator.name}`)
67+
debug('operatorMap::addOperatorDecorator', { name: decorator.name })
6868
this.decorators.set(decorator.name, decorator)
6969
}
7070

@@ -110,15 +110,15 @@ export default class OperatorMap {
110110
const decoratorName = opName.slice(0, firstDecoratorIndex)
111111
const decorator = this.decorators.get(decoratorName)
112112
if (!decorator) {
113-
debug(`operatorMap::get invalid decorator named ${decoratorName}`)
113+
debug('operatorMap::get invalid decorator', { name: decoratorName })
114114
return null
115115
}
116116
// we're going to apply this later, use unshift since we'll apply in reverse order
117117
decorators.unshift(decorator)
118118
// continue looking for a known operator with the rest of the name
119119
opName = opName.slice(firstDecoratorIndex + 1)
120120
} else {
121-
debug(`operatorMap::get no operator named ${opName}`)
121+
debug('operatorMap::get no operator', { name: opName })
122122
return null
123123
}
124124
}

src/rule.js

+6-26
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ class Rule extends EventEmitter {
249249
return Promise.all(
250250
conditions.map((condition) => evaluateCondition(condition))
251251
).then((conditionResults) => {
252-
debug('rule::evaluateConditions results', conditionResults)
252+
debug('rule::evaluateConditions', { results: conditionResults })
253253
return method.call(conditionResults, (result) => result === true)
254254
})
255255
}
@@ -274,36 +274,16 @@ class Rule extends EventEmitter {
274274
// this also covers the 'not' case which should only ever have a single condition
275275
return evaluateCondition(conditions[0])
276276
}
277-
let method = Array.prototype.some
278-
if (operator === 'all') {
279-
method = Array.prototype.every
280-
}
281277
const orderedSets = this.prioritizeConditions(conditions)
282-
let cursor = Promise.resolve()
278+
let cursor = Promise.resolve(operator === 'all')
283279
// use for() loop over Array.forEach to support IE8 without polyfill
284280
for (let i = 0; i < orderedSets.length; i++) {
285281
const set = orderedSets[i]
286-
let stop = false
287282
cursor = cursor.then((setResult) => {
288-
// after the first set succeeds, don't fire off the remaining promises
289-
if ((operator === 'any' && setResult === true) || stop) {
290-
debug(
291-
'prioritizeAndRun::detected truthy result; skipping remaining conditions'
292-
)
293-
stop = true
294-
return true
295-
}
296-
297-
// after the first set fails, don't fire off the remaining promises
298-
if ((operator === 'all' && setResult === false) || stop) {
299-
debug(
300-
'prioritizeAndRun::detected falsey result; skipping remaining conditions'
301-
)
302-
stop = true
303-
return false
304-
}
305-
// all conditions passed; proceed with running next set in parallel
306-
return evaluateConditions(set, method)
283+
// rely on the short-circuiting behavior of || and && to avoid evaluating subsequent conditions
284+
return operator === 'any'
285+
? (setResult || evaluateConditions(set, Array.prototype.some))
286+
: (setResult && evaluateConditions(set, Array.prototype.every))
307287
})
308288
}
309289
return cursor

test/engine-operator-map.test.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ describe('Engine Operator Map', () => {
7777
})
7878

7979
it('the swap decorator', () => {
80-
const factValue = 1;
81-
const jsonValue = [1, 2, 3];
80+
const factValue = 1
81+
const jsonValue = [1, 2, 3]
8282

83-
const op = engine.operators.get('swap:contains');
83+
const op = engine.operators.get('swap:contains')
8484
expect(op.evaluate(factValue, jsonValue)).to.be.true()
8585
})
8686
})

0 commit comments

Comments
 (0)