json-rules-engine
Advanced tools
Comparing version 1.3.1 to 1.4.0
@@ -0,1 +1,11 @@ | ||
1.4.0 / 2017-01-23 | ||
================== | ||
* Add `allowUndefinedFacts` engine option | ||
1.3.1 / 2017-01-16 | ||
================== | ||
* Bump object-hash dependency to latest | ||
1.3.0 / 2016-10-24 | ||
@@ -2,0 +12,0 @@ ================== |
@@ -15,2 +15,4 @@ 'use strict'; | ||
var _errors = require('./errors'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -64,3 +66,3 @@ | ||
if (fact === undefined) { | ||
throw new Error('Undefined fact: ' + factId); | ||
throw new _errors.UndefinedFactError('Undefined fact: ' + factId); | ||
} | ||
@@ -67,0 +69,0 @@ return fact; |
@@ -63,2 +63,3 @@ 'use strict'; | ||
var rules = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
@@ -70,2 +71,3 @@ _classCallCheck(this, Engine); | ||
_this.rules = []; | ||
_this.allowUndefinedFacts = options.allowUndefinedFacts || false; | ||
_this.operators = new Map(); | ||
@@ -250,3 +252,3 @@ _this.facts = new Map(); | ||
function evaluateRules(_x2, _x3) { | ||
function evaluateRules(_x3, _x4) { | ||
return _ref.apply(this, arguments); | ||
@@ -253,0 +255,0 @@ } |
@@ -8,4 +8,4 @@ 'use strict'; | ||
exports.default = function (rules) { | ||
return new _engine2.default(rules); | ||
exports.default = function (rules, options) { | ||
return new _engine2.default(rules, options); | ||
}; | ||
@@ -12,0 +12,0 @@ |
@@ -199,3 +199,3 @@ 'use strict'; | ||
var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee(condition) { | ||
var comparisonValue, subConditions, passes; | ||
var comparisonValue, passes, subConditions; | ||
return regeneratorRuntime.wrap(function _callee$(_context) { | ||
@@ -206,5 +206,6 @@ while (1) { | ||
comparisonValue = void 0; | ||
passes = void 0; | ||
if (!condition.isBooleanOperator()) { | ||
_context.next = 14; | ||
_context.next = 15; | ||
break; | ||
@@ -216,39 +217,64 @@ } | ||
if (!(condition.operator === 'all')) { | ||
_context.next = 9; | ||
_context.next = 10; | ||
break; | ||
} | ||
_context.next = 6; | ||
_context.next = 7; | ||
return all(subConditions); | ||
case 6: | ||
case 7: | ||
comparisonValue = _context.sent; | ||
_context.next = 12; | ||
_context.next = 13; | ||
break; | ||
case 9: | ||
_context.next = 11; | ||
case 10: | ||
_context.next = 12; | ||
return any(subConditions); | ||
case 11: | ||
case 12: | ||
comparisonValue = _context.sent; | ||
case 12: | ||
_context.next = 17; | ||
case 13: | ||
_context.next = 28; | ||
break; | ||
case 14: | ||
_context.next = 16; | ||
case 15: | ||
_context.prev = 15; | ||
_context.next = 18; | ||
return almanac.factValue(condition.fact, condition.params); | ||
case 16: | ||
case 18: | ||
comparisonValue = _context.sent; | ||
_context.next = 28; | ||
break; | ||
case 17: | ||
_context.next = 19; | ||
case 21: | ||
_context.prev = 21; | ||
_context.t0 = _context['catch'](15); | ||
if (!(_this3.engine.allowUndefinedFacts && _context.t0.code === 'UNDEFINED_FACT')) { | ||
_context.next = 27; | ||
break; | ||
} | ||
passes = false; | ||
_context.next = 28; | ||
break; | ||
case 27: | ||
throw _context.t0; | ||
case 28: | ||
if (!(passes === undefined)) { | ||
_context.next = 32; | ||
break; | ||
} | ||
_context.next = 31; | ||
return condition.evaluate(comparisonValue, _this3.engine.operators); | ||
case 19: | ||
case 31: | ||
passes = _context.sent; | ||
case 32: | ||
if (passes) { | ||
@@ -261,3 +287,3 @@ _this3.emit('success', _this3.event, almanac); | ||
case 22: | ||
case 34: | ||
case 'end': | ||
@@ -267,3 +293,3 @@ return _context.stop(); | ||
} | ||
}, _callee, _this3); | ||
}, _callee, _this3, [[15, 21]]); | ||
})); | ||
@@ -270,0 +296,0 @@ |
@@ -7,3 +7,3 @@ # Engine | ||
### constructor([Array rules]) | ||
### constructor([Array rules], Object [options]) | ||
@@ -17,4 +17,16 @@ ```js | ||
let engine = new Engine([Array rules]) | ||
// initialize with options | ||
let options = { | ||
allowUndefinedFacts: false | ||
}; | ||
let engine = new Engine([Array rules], options) | ||
``` | ||
#### Options | ||
`allowUndefinedFacts` - By default, when a running engine encounters an undefined fact, | ||
an exception is thrown. Turning this option on will cause the engine to treat | ||
undefined facts as falsey conditions. (default: false) | ||
### engine.addFact(String id, Function [definitionFunc], Object [options]) | ||
@@ -21,0 +33,0 @@ |
@@ -64,4 +64,6 @@ # Walkthrough | ||
Facts are constant values or pure functions. Using the current example, if the engine were to be run, it would throw an error: "Undefined fact: 'age'". So let's define some facts! | ||
Facts are constant values or pure functions. Using the current example, if the engine were to be run, it would throw an exception: `Undefined fact:'age'` (note: this behavior can be disable via [engine options](./engine.md#Options)). | ||
Let's define some facts: | ||
```js | ||
@@ -68,0 +70,0 @@ |
{ | ||
"name": "json-rules-engine", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"description": "Rules Engine expressed in simple json", | ||
@@ -27,2 +27,4 @@ "main": "dist/index.js", | ||
"globals": [ | ||
"context", | ||
"xcontext", | ||
"describe", | ||
@@ -29,0 +31,0 @@ "xdescribe", |
@@ -45,17 +45,21 @@ 'use strict' | ||
} | ||
let baseConditions = { | ||
any: [{ | ||
fact: 'eligibilityField', | ||
operator: 'lessThan', | ||
params: { | ||
eligibilityId: 1, | ||
field: 'age' | ||
}, | ||
value: 50 | ||
}] | ||
function baseConditions () { | ||
return { | ||
any: [{ | ||
fact: 'eligibilityField', | ||
operator: 'lessThan', | ||
params: { | ||
eligibilityId: 1, | ||
field: 'age' | ||
}, | ||
value: 50 | ||
}] | ||
} | ||
} | ||
let eventSpy = sinon.spy() | ||
function setup (conditions = baseConditions) { | ||
eventSpy.reset() | ||
engine = engineFactory() | ||
let successSpy = sinon.spy() | ||
let failureSpy = sinon.spy() | ||
function setup (conditions = baseConditions(), engineOptions = {}) { | ||
successSpy.reset() | ||
failureSpy.reset() | ||
engine = engineFactory([], engineOptions) | ||
let rule = factories.rule({ conditions, event }) | ||
@@ -65,5 +69,50 @@ engine.addRule(rule) | ||
engine.addFact('eligibilityData', eligibilityData) | ||
engine.on('success', eventSpy) | ||
engine.on('success', successSpy) | ||
engine.on('failure', failureSpy) | ||
} | ||
describe('options', () => { | ||
describe('options.allowUndefinedFacts', () => { | ||
it('throws when fact is undefined by default', async () => { | ||
let conditions = Object.assign({}, baseConditions()) | ||
conditions.any.push({ | ||
fact: 'undefined-fact', | ||
operator: 'equal', | ||
value: true | ||
}) | ||
setup(conditions) | ||
return expect(engine.run()).to.be.rejectedWith(/Undefined fact: undefined-fact/) | ||
}) | ||
context('treats undefined facts as falsey when allowUndefinedFacts is set', () => { | ||
it('emits "success" when the condition succeeds', async () => { | ||
let conditions = Object.assign({}, baseConditions()) | ||
conditions.any.push({ | ||
fact: 'undefined-fact', | ||
operator: 'equal', | ||
value: true | ||
}) | ||
setup(conditions, { allowUndefinedFacts: true }) | ||
await engine.run() | ||
expect(successSpy).to.have.been.called | ||
expect(failureSpy).to.not.have.been.called | ||
}) | ||
it('emits "failure" when the condition fails', async () => { | ||
let conditions = Object.assign({}, baseConditions()) | ||
conditions.any.push({ | ||
fact: 'undefined-fact', | ||
operator: 'equal', | ||
value: true | ||
}) | ||
conditions.any[0].params.eligibilityId = 2 | ||
setup(conditions, { allowUndefinedFacts: true }) | ||
await engine.run() | ||
expect(successSpy).to.not.have.been.called | ||
expect(failureSpy).to.have.been.called | ||
}) | ||
}) | ||
}) | ||
}) | ||
describe('params', () => { | ||
@@ -73,11 +122,11 @@ it('emits when the condition is met', async () => { | ||
await engine.run() | ||
expect(eventSpy).to.have.been.calledWith(event) | ||
expect(successSpy).to.have.been.calledWith(event) | ||
}) | ||
it('does not emit when the condition fails', async () => { | ||
let conditions = Object.assign({}, baseConditions) | ||
let conditions = Object.assign({}, baseConditions()) | ||
conditions.any[0].params.eligibilityId = 2 | ||
setup(conditions) | ||
await engine.run() | ||
expect(eventSpy).to.not.have.been.called | ||
expect(successSpy).to.not.have.been.called | ||
}) | ||
@@ -103,3 +152,3 @@ }) | ||
await engine.run() | ||
expect(eventSpy).to.have.been.calledWith(event) | ||
expect(successSpy).to.have.been.calledWith(event) | ||
}) | ||
@@ -112,3 +161,3 @@ | ||
await engine.run() | ||
expect(eventSpy).to.not.have.been.called | ||
expect(successSpy).to.not.have.been.called | ||
}) | ||
@@ -123,3 +172,3 @@ | ||
await engine.run() | ||
expect(eventSpy).to.have.been.calledWith(event) | ||
expect(successSpy).to.have.been.calledWith(event) | ||
}) | ||
@@ -134,3 +183,3 @@ | ||
await engine.run() | ||
expect(eventSpy).to.not.have.been.calledWith(event) | ||
expect(successSpy).to.not.have.been.calledWith(event) | ||
}) | ||
@@ -145,3 +194,3 @@ | ||
await engine.run() | ||
expect(eventSpy).to.have.been.calledWith(event) | ||
expect(successSpy).to.have.been.calledWith(event) | ||
}) | ||
@@ -156,3 +205,3 @@ | ||
await engine.run() | ||
expect(eventSpy).to.have.been.calledWith(event) | ||
expect(successSpy).to.have.been.calledWith(event) | ||
}) | ||
@@ -173,3 +222,3 @@ }) | ||
await engine.run() | ||
expect(eventSpy).to.have.been.called | ||
expect(successSpy).to.have.been.called | ||
}) | ||
@@ -186,3 +235,3 @@ }) | ||
await engine.run() | ||
expect(eventSpy).to.have.been.called | ||
expect(successSpy).to.have.been.called | ||
}) | ||
@@ -197,5 +246,5 @@ | ||
await engine.run() | ||
expect(eventSpy).to.not.have.been.called | ||
expect(successSpy).to.not.have.been.called | ||
}) | ||
}) | ||
}) |
196242
60
4679