Skip to content

Commit 2e4ec69

Browse files
author
Christopher Pardy
committed
Add documentation around shared conditions
Add documentation covering the use and advantages of shared conditions. Specifically on the ability to re-use the same condition across multiple rules.
1 parent b19cff2 commit 2e4ec69

File tree

4 files changed

+234
-2
lines changed

4 files changed

+234
-2
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ A rules engine expressed in JSON
2929

3030
* Rules expressed in simple, easy to read JSON
3131
* Full support for ```ALL``` and ```ANY``` boolean operators, including recursive nesting
32+
* Rule composition / inheritance with the use of shared conditions
3233
* Fast by default, faster with configuration; priority levels and cache settings for fine tuning performance
3334
* Secure; no use of eval()
3435
* Isomorphic; runs in node and browser

docs/engine.md

+67
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ The Engine stores and executes rules, emits events, and maintains state.
1212
* [engine.removeRule(Rule instance | String ruleId)](#engineremoverulerule-instance)
1313
* [engine.addOperator(String operatorName, Function evaluateFunc(factValue, jsonValue))](#engineaddoperatorstring-operatorname-function-evaluatefuncfactvalue-jsonvalue)
1414
* [engine.removeOperator(String operatorName)](#engineremoveoperatorstring-operatorname)
15+
* [engine.addSharedCondition(String name, Object conditions)](#engineaddsharedconditionsstring-name-object-conditions)
16+
* [engine.removeSharedCondition(String name)](#engineremovesharedcondtionsstring-name)
1517
* [engine.run([Object facts], [Object options]) -> Promise ({ events: [], failureEvents: [], almanac: Almanac, results: [], failureResults: []})](#enginerunobject-facts-object-options---promise--events--failureevents--almanac-almanac-results--failureresults-)
1618
* [engine.stop() -> Engine](#enginestop---engine)
1719
* [engine.on('success', Function(Object event, Almanac almanac, RuleResult ruleResult))](#engineonsuccess-functionobject-event-almanac-almanac-ruleresult-ruleresult)
@@ -172,6 +174,71 @@ engine.addOperator('startsWithLetter', (factValue, jsonValue) => {
172174
engine.removeOperator('startsWithLetter');
173175
```
174176

177+
### engine.addSharedCondition(String name, Object conditions)
178+
179+
Adds a shared condition to the engine. Rules may include references to this shared condition.
180+
181+
```javascript
182+
engine.addSharedCondition('validLogin', {
183+
all: [
184+
{
185+
operator: 'notEqual',
186+
fact: 'loginToken',
187+
value: null
188+
},
189+
{
190+
operator: 'greaterThan',
191+
fact: 'loginToken',
192+
path: '$.expirationTime',
193+
value: { fact: 'now' }
194+
}
195+
]
196+
});
197+
198+
engine.addRule({
199+
condtions: {
200+
all: [
201+
{
202+
uses: 'validLogin'
203+
},
204+
{
205+
operator: 'contains',
206+
fact: 'loginToken',
207+
path: '$.role',
208+
value: 'admin'
209+
}
210+
]
211+
},
212+
event: {
213+
type: 'AdminAccessAllowed'
214+
}
215+
})
216+
217+
```
218+
219+
### engine.removeSharedCondition(String name)
220+
221+
Removes the shared condition that was previously added.
222+
223+
```javascript
224+
engine.addSharedCondition('validLogin', {
225+
all: [
226+
{
227+
operator: 'notEqual',
228+
fact: 'loginToken',
229+
value: null
230+
},
231+
{
232+
operator: 'greaterThan',
233+
fact: 'loginToken',
234+
path: '$.expirationTime',
235+
value: { fact: 'now' }
236+
}
237+
]
238+
});
239+
240+
engine.removeSharedCondition('validLogin');
241+
```
175242

176243

177244
### engine.run([Object facts], [Object options]) -> Promise ({ events: [], failureEvents: [], almanac: Almanac, results: [], failureResults: []})

docs/rules.md

+26-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Rules contain a set of _conditions_ and a single _event_. When the engine is ru
1515
* [Conditions](#conditions)
1616
* [Basic conditions](#basic-conditions)
1717
* [Boolean expressions: all, any, and not](#boolean-expressions-all-any-and-not)
18+
* [Shared conditions](#shared-conditions)
1819
* [Condition helpers: params](#condition-helpers-params)
1920
* [Condition helpers: path](#condition-helpers-path)
2021
* [Condition helpers: custom path resolver](#condition-helpers-custom-path-resolver)
@@ -136,7 +137,7 @@ See the [hello-world](../examples/01-hello-world.js) example.
136137

137138
### Boolean expressions: `all`, `any`, and `not`
138139

139-
Each rule's conditions *must* have an `all` or `any` operator containing an array of conditions at its root or a `not` operator containing a single condition. The `all` operator specifies that all conditions contained within must be truthy for the rule to be considered a `success`. The `any` operator only requires one condition to be truthy for the rule to succeed. The `not` operator will negate whatever condition it contains.
140+
Each rule's conditions *must* reference a shared condition or have an `all` or `any` operator containing an array of conditions at its root or a `not` operator containing a single condition. The `all` operator specifies that all conditions contained within must be truthy for the rule to be considered a `success`. The `any` operator only requires one condition to be truthy for the rule to succeed. The `not` operator will negate whatever condition it contains.
140141

141142
```js
142143
// all:
@@ -174,7 +175,30 @@ let rule = new Rule({
174175
})
175176
```
176177

177-
Notice in the second example how `all`, `any`, and 'not' can be nested within one another to produce complex boolean expressions. See the [nested-boolean-logic](../examples/02-nested-boolean-logic.js) example.
178+
Notice in the second example how `all`, `any`, and `not` can be nested within one another to produce complex boolean expressions. See the [nested-boolean-logic](../examples/02-nested-boolean-logic.js) example.
179+
180+
### Shared Conditions
181+
182+
Rules my reference shared conditions based on their name.
183+
184+
```js
185+
let rule = new Rule({
186+
conditions: {
187+
all: [
188+
{ uses: 'sharedCondition' },
189+
{ /* additional condition */ }
190+
]
191+
}
192+
})
193+
```
194+
195+
Before running the rule the shared condition should be added to the engine.
196+
197+
```js
198+
engine.addSharedCondition('sharedCondition', { /* conditions */ });
199+
```
200+
201+
Shared conditions must start with `all`, `any`, `not`, or reference a shared condition.
178202

179203
### Condition helpers: `params`
180204

examples/10-shared-conditions.js

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
'use strict'
2+
/*
3+
* This is an advanced example demonstrating rules that passed based off the
4+
* results of other rules by adding runtime facts. It also demonstrates
5+
* accessing the runtime facts after engine execution.
6+
*
7+
* Usage:
8+
* node ./examples/10-shared-conditions.js
9+
*
10+
* For detailed output:
11+
* DEBUG=json-rules-engine node ./examples/10-shared-conditions.js
12+
*/
13+
14+
require('colors')
15+
const { Engine } = require('json-rules-engine')
16+
17+
async function start () {
18+
/**
19+
* Setup a new engine
20+
*/
21+
const engine = new Engine()
22+
23+
/**
24+
* Shared condition that will be used to determine if a user likes screwdrivers
25+
*/
26+
engine.addSharedCondition('screwdriverAficionado', {
27+
all: [
28+
{
29+
fact: 'drinksOrangeJuice',
30+
operator: 'equal',
31+
value: true
32+
},
33+
{
34+
fact: 'enjoysVodka',
35+
operator: 'equal',
36+
value: true
37+
}
38+
]
39+
})
40+
41+
/**
42+
* Rule for identifying people who should be invited to a screwdriver social
43+
* - Only invite people who enjoy screw drivers
44+
* - Only invite people who are sociable
45+
*/
46+
const inviteRule = {
47+
conditions: {
48+
all: [
49+
{
50+
uses: 'screwdriverAficionado'
51+
},
52+
{
53+
fact: 'isSociable',
54+
operator: 'equal',
55+
value: true
56+
}
57+
]
58+
},
59+
event: { type: 'invite-to-screwdriver-social' }
60+
}
61+
engine.addRule(inviteRule)
62+
63+
/**
64+
* Rule for identifying people who should be invited to the other social
65+
* - Only invite people who don't enjoy screw drivers
66+
* - Only invite people who are sociable
67+
*/
68+
const otherInviteRule = {
69+
conditions: {
70+
all: [
71+
{
72+
not: {
73+
uses: 'screwdriverAficionado'
74+
}
75+
},
76+
{
77+
fact: 'isSociable',
78+
operator: 'equal',
79+
value: true
80+
}
81+
]
82+
},
83+
event: { type: 'invite-to-other-social' }
84+
}
85+
engine.addRule(otherInviteRule)
86+
87+
/**
88+
* Register listeners with the engine for rule success and failure
89+
*/
90+
engine
91+
.on('success', async (event, almanac) => {
92+
const accountId = await almanac.factValue('accountId')
93+
console.log(
94+
`${accountId}` +
95+
'DID'.green +
96+
` meet conditions for the ${event.type.underline} rule.`
97+
)
98+
})
99+
.on('failure', async (event, almanac) => {
100+
const accountId = await almanac.factValue('accountId')
101+
console.log(
102+
`${accountId} did ` +
103+
'NOT'.red +
104+
` meet conditions for the ${event.type.underline} rule.`
105+
)
106+
})
107+
108+
// define fact(s) known at runtime
109+
let facts = {
110+
accountId: 'washington',
111+
drinksOrangeJuice: true,
112+
enjoysVodka: true,
113+
isSociable: true
114+
}
115+
116+
// first run, using washington's facts
117+
await engine.run(facts)
118+
119+
facts = {
120+
accountId: 'jefferson',
121+
drinksOrangeJuice: true,
122+
enjoysVodka: false,
123+
isSociable: true,
124+
accountInfo: {}
125+
}
126+
127+
// second run, using jefferson's facts; facts & evaluation are independent of the first run
128+
await engine.run(facts)
129+
}
130+
131+
start()
132+
133+
/*
134+
* OUTPUT:
135+
*
136+
* washington DID meet conditions for the invite-to-screwdriver-social rule.
137+
* washington did NOT meet conditions for the invite-to-other-social rule.
138+
* jefferson did NOT meet conditions for the invite-to-screwdriver-social rule.
139+
* jefferson DID meet conditions for the invite-to-other-social rule.
140+
*/

0 commit comments

Comments
 (0)