Skip to content

Commit 9d51178

Browse files
authored
Merge pull request #390 from emilefokkemanavara/fix/352/rule-result-json
fix: improve typing of RuleResult
2 parents ae8fa05 + 1d00bef commit 9d51178

File tree

3 files changed

+123
-3
lines changed

3 files changed

+123
-3
lines changed

test/engine-event.test.js

+64
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ describe('Engine: event', () => {
2020
canOrderDrinks: true
2121
}
2222
}
23+
24+
const awesomeEvent = {
25+
type: 'awesome'
26+
}
2327
/**
2428
* sets up a simple 'any' rule with 2 conditions
2529
*/
@@ -92,6 +96,46 @@ describe('Engine: event', () => {
9296
engine.addFact('gender', 'male') // gender succeeds
9397
}
9498

99+
function setupWithConditionReference () {
100+
const conditionName = 'awesomeCondition'
101+
const conditions = {
102+
any: [{ condition: conditionName }]
103+
}
104+
engine = engineFactory()
105+
const ruleOptions = { conditions, event: awesomeEvent, priority: 100 }
106+
const rule = factories.rule(ruleOptions)
107+
engine.addRule(rule)
108+
engine.setCondition(conditionName, {
109+
all: [{
110+
name: 'over 21',
111+
fact: 'age',
112+
operator: 'greaterThanInclusive',
113+
value: 21
114+
}]
115+
})
116+
engine.addFact('age', 21)
117+
}
118+
119+
function setupWithUndefinedCondition () {
120+
const conditionName = 'conditionThatIsNotDefined'
121+
const conditions = {
122+
any: [
123+
{ condition: conditionName, name: 'nameOfTheUndefinedConditionReference' },
124+
{
125+
name: 'over 21',
126+
fact: 'age',
127+
operator: 'greaterThanInclusive',
128+
value: 21
129+
}
130+
]
131+
}
132+
engine = engineFactory([], { allowUndefinedConditions: true })
133+
const ruleOptions = { conditions, event: awesomeEvent, priority: 100 }
134+
const rule = factories.rule(ruleOptions)
135+
engine.addRule(rule)
136+
engine.addFact('age', 21)
137+
}
138+
95139
context('engine events: simple', () => {
96140
beforeEach(() => simpleSetup())
97141

@@ -602,4 +646,24 @@ describe('Engine: event', () => {
602646
expect(JSON.stringify(ruleResult)).to.equal(expected)
603647
})
604648
})
649+
650+
context('rule events: json serializing with condition reference', () => {
651+
beforeEach(() => setupWithConditionReference())
652+
it('serializes properties', async () => {
653+
const { results: [ruleResult] } = await engine.run()
654+
const expected = '{"conditions":{"priority":1,"any":[{"priority":1,"all":[{"name":"over 21","operator":"greaterThanInclusive","value":21,"fact":"age","factResult":21,"result":true}]}]},"event":{"type":"awesome"},"priority":100,"result":true}'
655+
expect(JSON.stringify(ruleResult)).to.equal(expected)
656+
})
657+
})
658+
659+
context('rule events: json serializing with condition reference that is undefined', () => {
660+
beforeEach(() => setupWithUndefinedCondition())
661+
it('serializes properties', async () => {
662+
const { results: [ruleResult] } = await engine.run()
663+
const { conditions: { any: [conditionReference] } } = ruleResult
664+
expect(conditionReference.result).to.equal(false)
665+
const expected = '{"conditions":{"priority":1,"any":[{"name":"nameOfTheUndefinedConditionReference","condition":"conditionThatIsNotDefined"},{"name":"over 21","operator":"greaterThanInclusive","value":21,"fact":"age","factResult":21,"result":true}]},"event":{"type":"awesome"},"priority":100,"result":true}'
666+
expect(JSON.stringify(ruleResult)).to.equal(expected)
667+
})
668+
})
605669
})

types/index.d.ts

+40-1
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,22 @@ export type RuleSerializable = Pick<
153153
"conditions" | "event" | "name" | "priority"
154154
>;
155155

156+
export type RuleResultSerializable = Pick<
157+
Required<RuleResult>,
158+
"name" | "event" | "priority" | "result"> & {
159+
conditions: TopLevelConditionResultSerializable
160+
}
161+
156162
export interface RuleResult {
157163
name: string;
158-
conditions: TopLevelCondition;
164+
conditions: TopLevelConditionResult;
159165
event?: Event;
160166
priority?: number;
161167
result: any;
168+
toJSON(): string;
169+
toJSON<T extends boolean>(
170+
stringify: T
171+
): T extends true ? string : RuleResultSerializable;
162172
}
163173

164174
export class Rule implements RuleProperties {
@@ -176,6 +186,14 @@ export class Rule implements RuleProperties {
176186
): T extends true ? string : RuleSerializable;
177187
}
178188

189+
interface BooleanConditionResultProperties {
190+
result?: boolean
191+
}
192+
193+
interface ConditionResultProperties extends BooleanConditionResultProperties {
194+
factResult?: unknown
195+
}
196+
179197
interface ConditionProperties {
180198
fact: string;
181199
operator: string;
@@ -186,25 +204,46 @@ interface ConditionProperties {
186204
name?: string;
187205
}
188206

207+
type ConditionPropertiesResult = ConditionProperties & ConditionResultProperties
208+
189209
type NestedCondition = ConditionProperties | TopLevelCondition;
210+
type NestedConditionResult = ConditionPropertiesResult | TopLevelConditionResult;
190211
type AllConditions = {
191212
all: NestedCondition[];
192213
name?: string;
193214
priority?: number;
194215
};
216+
type AllConditionsResult = AllConditions & {
217+
all: NestedConditionResult[]
218+
} & BooleanConditionResultProperties
195219
type AnyConditions = {
196220
any: NestedCondition[];
197221
name?: string;
198222
priority?: number;
199223
};
224+
type AnyConditionsResult = AnyConditions & {
225+
any: NestedConditionResult[]
226+
} & BooleanConditionResultProperties
200227
type NotConditions = { not: NestedCondition; name?: string; priority?: number };
228+
type NotConditionsResult = NotConditions & {not: NestedConditionResult} & BooleanConditionResultProperties;
201229
type ConditionReference = {
202230
condition: string;
203231
name?: string;
204232
priority?: number;
205233
};
234+
type ConditionReferenceResult = ConditionReference & BooleanConditionResultProperties
206235
export type TopLevelCondition =
207236
| AllConditions
208237
| AnyConditions
209238
| NotConditions
210239
| ConditionReference;
240+
export type TopLevelConditionResult =
241+
| AllConditionsResult
242+
| AnyConditionsResult
243+
| NotConditionsResult
244+
| ConditionReferenceResult
245+
export type TopLevelConditionResultSerializable =
246+
| AllConditionsResult
247+
| AnyConditionsResult
248+
| NotConditionsResult
249+
| ConditionReference

types/index.test-d.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import rulesEngine, {
1414
Rule,
1515
RuleProperties,
1616
RuleResult,
17-
RuleSerializable
17+
RuleSerializable,
18+
TopLevelConditionResult,
19+
AnyConditionsResult,
20+
AllConditionsResult,
21+
NotConditionsResult
1822
} from "../";
1923

2024
// setup basic fixture data
@@ -126,7 +130,20 @@ engine.on<{ foo: Array<string> }>('foo', (event, almanac, ruleResult) => {
126130
})
127131

128132
// Run the Engine
129-
expectType<Promise<EngineResult>>(engine.run({ displayMessage: true }));
133+
const result = engine.run({ displayMessage: true })
134+
expectType<Promise<EngineResult>>(result);
135+
136+
const topLevelConditionResult = result.then(r => r.results[0].conditions);
137+
expectType<Promise<TopLevelConditionResult>>(topLevelConditionResult)
138+
139+
const topLevelAnyConditionsResult = topLevelConditionResult.then(r => (r as AnyConditionsResult).result);
140+
expectType<Promise<boolean | undefined>>(topLevelAnyConditionsResult)
141+
142+
const topLevelAllConditionsResult = topLevelConditionResult.then(r => (r as AllConditionsResult).result);
143+
expectType<Promise<boolean | undefined>>(topLevelAllConditionsResult)
144+
145+
const topLevelNotConditionsResult = topLevelConditionResult.then(r => (r as NotConditionsResult).result);
146+
expectType<Promise<boolean | undefined>>(topLevelNotConditionsResult)
130147

131148
// Alamanac tests
132149
const almanac: Almanac = (await engine.run()).almanac;

0 commit comments

Comments
 (0)