A simple customizable conditions and rules engine that allows you to evaluate JSON documents describing one or multiple conditions.
Here is a very simple a example:
// Your own map of conditions (one resolver and one validator for each)
const conditions = {
StringEquals: {
resolver: StringEquals.Resolver,
validator: StringEquals.Validator,
},
}
// An engine that stores the dictionary of conditions
const engine = new DefaultEngine(conditions)
// And then, you can start evaluating inputs
const firstInput = {
type: `StringEquals`,
expected: `something`,
received: `something`,
}
const firstEvaluation = await engine.evaluate(input)
// firstEvaluation.passed === true
const secondInput = {
type: `StringEquals`,
expected: `something`,
received: `another thing`,
}
const secondEvaluation = await engine.evaluate(input)
// secondEvaluation.passed === false
The client using this library should generally create its own Engine along with its own Conditions (i.e. validators and resolvers) since they are really application-specific.
For the sake of illustration, there is a working implementation named DefaultEngine
in the src/defaults
folder.
Konditions was built as a pet project but also to answer my own needs for another project. I wanted to build a Policy-based Access Control with policies defined in JSON objects inspired by AWS IAM Policies.
Each policy statement can have a condition
field in which the user defines several required assessments to pass. I did not like the format used by AWS, so I created Konditions
.
Please note, this is not yet production-ready nor battle-tested. Pull requests are welcomed!
Engine
– the entry point for the clients with anevaluate
methodRegistry
– an object that holdsCondition
as values and theirtype
as keysResolution
– the final output of an evaluation, with a boolean propertypassed
, and some addition properties depending on the resultCondition
– an simple object containing aresolver
function and avalidator
functionInput Props
– the raw JSON object with atype
property to allow theEngine
to evaluate itProps
– the props expected by theResolver
of a given conditiontype
Validator
– takes candidate props for a given condition and ensures it is fully correct in order to return the castProps
Resolver
– takes validated and castProps
and returns aResolution
after computation
- Decide on what condition types you want to cover
- Decide on what output you want to have (its shape)
- Create a resolver and validator for each condition type with the right output
- Instantiate an engine with your condition registry (the object)
- Evaluate some input props
- Enjoy!
The Engine
passes itself by reference to both a condition's validator and resolver. If you want to create condition types that hold children, you can evaluate them directly from your condition's resolver.
Here is an example from the DefaultEngine
. It is an Every
(i.e. And
, All
) condition which means all the children input props must be evaluated and must pass (if one fails, it fails too).
export interface Props {
conditions: any[]
}
export const Resolver: ConditionResolver<Resolution, Props> = async (props, engine) => {
const results = await Promise.all(props.conditions.map((child) => engine.evaluate(child)))
const resolution = makeResolution(results.every((val) => val.passed === true))
resolution.resolutions = results
return resolution
}
And as a test:
test(`should pass when all conditions are true`, async () => {
const engine = new DefaultEngine({
Every: {
resolver: Resolver,
validator: Validator,
},
Pass: {
resolver: Passs.Resolver,
validator: Passs.Validator,
},
})
const resolution = await Resolver(
{
// This array holds all the children conditions
// all of them must pass
conditions: [
{
// a likelihood of 1 means it will always pass
// it's a helper condition type for tests,
// you would obviously use real conditions here
type: `Pass`,
likelihood: 1,
},
{
type: `Pass`,
likelihood: 1,
},
{
type: `Pass`,
likelihood: 1,
},
{
type: `Pass`,
likelihood: 1,
},
{
type: `Pass`,
likelihood: 1,
},
],
},
engine
)
expect(resolution.passed).toStrictEqual(true)
})
If any of the Pass
conditions did not pass, that evaluation would fail. The same can be done for Some
(i.e. Or
, Any
).