Skip to content

fix: improve typing of RuleResult #390

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions test/engine-event.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ describe('Engine: event', () => {
canOrderDrinks: true
}
}

const awesomeEvent = {
type: 'awesome'
}
/**
* sets up a simple 'any' rule with 2 conditions
*/
Expand Down Expand Up @@ -92,6 +96,46 @@ describe('Engine: event', () => {
engine.addFact('gender', 'male') // gender succeeds
}

function setupWithConditionReference () {
const conditionName = 'awesomeCondition'
const conditions = {
any: [{ condition: conditionName }]
}
engine = engineFactory()
const ruleOptions = { conditions, event: awesomeEvent, priority: 100 }
const rule = factories.rule(ruleOptions)
engine.addRule(rule)
engine.setCondition(conditionName, {
all: [{
name: 'over 21',
fact: 'age',
operator: 'greaterThanInclusive',
value: 21
}]
})
engine.addFact('age', 21)
}

function setupWithUndefinedCondition () {
const conditionName = 'conditionThatIsNotDefined'
const conditions = {
any: [
{ condition: conditionName, name: 'nameOfTheUndefinedConditionReference' },
{
name: 'over 21',
fact: 'age',
operator: 'greaterThanInclusive',
value: 21
}
]
}
engine = engineFactory([], { allowUndefinedConditions: true })
const ruleOptions = { conditions, event: awesomeEvent, priority: 100 }
const rule = factories.rule(ruleOptions)
engine.addRule(rule)
engine.addFact('age', 21)
}

context('engine events: simple', () => {
beforeEach(() => simpleSetup())

Expand Down Expand Up @@ -602,4 +646,24 @@ describe('Engine: event', () => {
expect(JSON.stringify(ruleResult)).to.equal(expected)
})
})

context('rule events: json serializing with condition reference', () => {
beforeEach(() => setupWithConditionReference())
it('serializes properties', async () => {
const { results: [ruleResult] } = await engine.run()
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}'
expect(JSON.stringify(ruleResult)).to.equal(expected)
})
})

context('rule events: json serializing with condition reference that is undefined', () => {
beforeEach(() => setupWithUndefinedCondition())
it('serializes properties', async () => {
const { results: [ruleResult] } = await engine.run()
const { conditions: { any: [conditionReference] } } = ruleResult
expect(conditionReference.result).to.equal(false)
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}'
expect(JSON.stringify(ruleResult)).to.equal(expected)
})
})
})
41 changes: 40 additions & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,22 @@ export type RuleSerializable = Pick<
"conditions" | "event" | "name" | "priority"
>;

export type RuleResultSerializable = Pick<
Required<RuleResult>,
"name" | "event" | "priority" | "result"> & {
conditions: TopLevelConditionResultSerializable
}

export interface RuleResult {
name: string;
conditions: TopLevelCondition;
conditions: TopLevelConditionResult;
event?: Event;
priority?: number;
result: any;
toJSON(): string;
toJSON<T extends boolean>(
stringify: T
): T extends true ? string : RuleResultSerializable;
}

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

interface BooleanConditionResultProperties {
result?: boolean
}

interface ConditionResultProperties extends BooleanConditionResultProperties {
factResult?: unknown
}

interface ConditionProperties {
fact: string;
operator: string;
Expand All @@ -186,25 +204,46 @@ interface ConditionProperties {
name?: string;
}

type ConditionPropertiesResult = ConditionProperties & ConditionResultProperties

type NestedCondition = ConditionProperties | TopLevelCondition;
type NestedConditionResult = ConditionPropertiesResult | TopLevelConditionResult;
type AllConditions = {
all: NestedCondition[];
name?: string;
priority?: number;
};
type AllConditionsResult = AllConditions & {
all: NestedConditionResult[]
} & BooleanConditionResultProperties
type AnyConditions = {
any: NestedCondition[];
name?: string;
priority?: number;
};
type AnyConditionsResult = AnyConditions & {
any: NestedConditionResult[]
} & BooleanConditionResultProperties
type NotConditions = { not: NestedCondition; name?: string; priority?: number };
type NotConditionsResult = NotConditions & {not: NestedConditionResult} & BooleanConditionResultProperties;
type ConditionReference = {
condition: string;
name?: string;
priority?: number;
};
type ConditionReferenceResult = ConditionReference & BooleanConditionResultProperties
export type TopLevelCondition =
| AllConditions
| AnyConditions
| NotConditions
| ConditionReference;
export type TopLevelConditionResult =
| AllConditionsResult
| AnyConditionsResult
| NotConditionsResult
| ConditionReferenceResult
export type TopLevelConditionResultSerializable =
| AllConditionsResult
| AnyConditionsResult
| NotConditionsResult
| ConditionReference
21 changes: 19 additions & 2 deletions types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import rulesEngine, {
Rule,
RuleProperties,
RuleResult,
RuleSerializable
RuleSerializable,
TopLevelConditionResult,
AnyConditionsResult,
AllConditionsResult,
NotConditionsResult
} from "../";

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

// Run the Engine
expectType<Promise<EngineResult>>(engine.run({ displayMessage: true }));
const result = engine.run({ displayMessage: true })
expectType<Promise<EngineResult>>(result);

const topLevelConditionResult = result.then(r => r.results[0].conditions);
expectType<Promise<TopLevelConditionResult>>(topLevelConditionResult)

const topLevelAnyConditionsResult = topLevelConditionResult.then(r => (r as AnyConditionsResult).result);
expectType<Promise<boolean | undefined>>(topLevelAnyConditionsResult)

const topLevelAllConditionsResult = topLevelConditionResult.then(r => (r as AllConditionsResult).result);
expectType<Promise<boolean | undefined>>(topLevelAllConditionsResult)

const topLevelNotConditionsResult = topLevelConditionResult.then(r => (r as NotConditionsResult).result);
expectType<Promise<boolean | undefined>>(topLevelNotConditionsResult)

// Alamanac tests
const almanac: Almanac = (await engine.run()).almanac;
Expand Down
Loading