Skip to content

Commit 371243e

Browse files
author
Christopher Pardy
committed
Add documentation around conditions
Add documentation covering the use and advantages of condition sharing. Specifically on the ability to re-use the same condition across multiple rules.
1 parent 96f5c17 commit 371243e

File tree

3 files changed

+237
-2
lines changed

3 files changed

+237
-2
lines changed

docs/engine.md

Lines changed: 72 additions & 0 deletions
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.setCondition(String name, Object conditions)](#enginesetconditionstring-name-object-conditions)
16+
* [engine.removeCondition(String name)](#engineremovecondtionstring-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)
@@ -43,6 +45,11 @@ let engine = new Engine([Array rules], options)
4345
an exception is thrown. Turning this option on will cause the engine to treat
4446
undefined facts as `undefined`. (default: false)
4547

48+
`allowUndefinedConditions` - By default, when a running engine encounters a
49+
condition reference that cannot be resolved an exception is thrown. Turning
50+
this option on will cause the engine to treat unresolvable condition references
51+
as failed conditions. (default: false)
52+
4653
`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.
4754

4855
### engine.addFact(String id, Function [definitionFunc], Object [options])
@@ -172,6 +179,71 @@ engine.addOperator('startsWithLetter', (factValue, jsonValue) => {
172179
engine.removeOperator('startsWithLetter');
173180
```
174181

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

176248

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

docs/rules.md

Lines changed: 26 additions & 2 deletions
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+
* [Condition Reference](#condition-reference)
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* have an `all` or `any` operator containing an array of conditions at its root, a `not` operator containing a single condition, or a condition reference. 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+
### Condition Reference
181+
182+
Rules may reference conditions based on their name.
183+
184+
```js
185+
let rule = new Rule({
186+
conditions: {
187+
all: [
188+
{ condition: 'conditionName' },
189+
{ /* additional condition */ }
190+
]
191+
}
192+
})
193+
```
194+
195+
Before running the rule the condition should be added to the engine.
196+
197+
```js
198+
engine.setCondition('conditionName', { /* conditions */ });
199+
```
200+
201+
Conditions must start with `all`, `any`, `not`, or reference a condition.
178202

179203
### Condition helpers: `params`
180204

examples/10-condition-sharing.js

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

0 commit comments

Comments
 (0)