Drules is a simple rule engine for Dart. It allows you to define rules in Json format and execute them against a given context. The rules are defined in a simple Json format and can be easily extended to support more complex rules.
To use Drules in your Dart code, add it as a dependency in your project.
dart pub add drules
A simple usage example:
import 'package:drules/drules.dart';
void main() async {
var jsonRules =
[
'''
{
"name": "rule1",
"conditions": {
"operator": ">",
"operands": ["age", 18]
},
"actionInfo": {
"onSuccess": {
"operation": "print",
"parameters": ["You are an adult"]
}
}
}
''',
'''
{
"name": "rule2",
"conditions": {
"operator": "<",
"operands": ["age", 18]
},
"actionInfo": {
"onSuccess": {
"operation": "print",
"parameters": ["You are a child as your age is ${age}"]
}
}
}
'''
];
var ruleRepository = StringRuleRepository(jsonRules);
var ruleEngine = RuleEngine(ruleRepository);
var context = RuleContext();
context.addFact("age", 20);
await ruleEngine.run(context);
}
- Simple rule definition in Json format
- Support for basic conditions and actions
- Easily extensible to support more complex rules
- Support for asynchronous actions
- Support for custom actions
- Support for custom conditions
A rule is defined in a simple Json format. A rule consists of the following fields:
id
: The unique identifier of the rule. The id will be automatically generated if not provided.name
: The name of the rule. This is optional and can be used for debugging purposes.priority
: The priority of the rule. The rules are executed in the order of their priority. The default priority is 0.enabled
: A flag to indicate whether the rule is enabled or not. The default value is true.conditions
: The conditions that need to be satisfied for the rule to be executed. More details about conditions are provided below.actionInfo
: Information about the actions to be executed when the rule is activated. The actionInfo is defined in the following format:onSuccess
: The action to be executed when the rule is successful.operation
: The operation to be executed.parameters
: The parameters to be passed to the operation.
onFailure
: The action to be executed when the rule execution fails due to an error.operation
: The operation to be executed.parameters
: The parameters to be passed to the operation.
A condition consists of the following fields:
-
operator
: The operator to be used to evaluate the condition. It can be one of the following built-in operators or any user-defined operator:==
: Equal to!=
: Not equal to>
: Greater than<
: Less than>=
: Greater than or equal to<=
: Less than or equal to!
: Negation of the operandall
: All operands must be trueany
: Any operand must be truenone
: None of the operands must be truecontains
: The first operand must contain the second operandstartsWith
: The first operand must start with the second operandendsWith
: The first operand must end with the second operandmatches
: The first operand must match the regular expression in the second operandexpression
: The first operand must evaluate to true using the expression in the second operand
-
operands
: A list of operands to be used in the evaluation.
{
"operator": ">",
"operands": ["age", 18]
}
{
"operator": "all",
"operands": [
{
"operator": ">",
"operands": ["age", 18]
},
{
"operator": "<",
"operands": ["age", 60]
}
]
}
Drules supports expression conditions. The expression condition allows you to define a custom expression to evaluate the condition. It uses the template_expressions package to evaluate the expression.
If you want to use Dart objects in the expression, you have to pass the MemberAccessor
object to the RuleContext
object. The MemberAccessor
object provides access to the fields/methods of the Dart objects in the expression.
// user defined object
class Counter {
int _value;
Counter(this._value);
void increment() {
_value++;
}
int get value => _value;
}
var context = RuleContext(resolve: [
MemberAccessor<Counter>({
'value': (c) => c.value,
'increment': (c) => c.increment,
}),
]);
context.addFact('counter', Counter(2));
The expression condition is defined in the following format:
{
"operator": "expression",
"operands": ["counter.value == 2"]
}
A custom condition can be defined by extending the Condition
class. It must be registered with the RuleEngine
object to be used in the rules.
ruleEngine.registerCondition(CustomCondition('isEven', (operands, context) {
return operands[0] % 2 == 0;
}));
Then it can be used in the rules as follows:
{
"operator": "isEven",
"operands": [2]
}
An action consists of the following fields:
-
operation
: The operation to be executed. The operation can be one of the following built-in operations or any user-defined operation:print
: Prints the output to the console.expression
: Evaluates a Dart expression.stop
: Stops further execution of the rule or other actions in the pipeline.chain
: All actions in the chain must be executed in the order they are defined.parallel
: All actions in the parallel block must be executed in parallel.pipe
: The output of one action is passed as input to the next action.
-
parameters
: The parameters to be passed to the operation.
{
"operation": "print",
"parameters": ["You are an adult"]
}
Drules supports expression actions. The expression action allows you to define a custom expression to evaluate the action. It uses the template_expressions package to evaluate the expression.
Similar to the expression condition, you have to set the MemberAccessor
object in the RuleContext
object to use Dart objects in the expression.
{
"operation": "expression",
"parameters": ["counter.increment()"]
}
A custom action can be defined by extending the Action
class or by using the CustomAction
class. It must be registered with the RuleEngine
object to be used in the rules.
ruleEngine.registerAction(CustomAction('log', (parameters, context) {
print(parameters[0]);
}));
Then it can be used in the rules as follows:
{
"operation": "log",
"parameters": ["You are an adult"]
}
The RuleContext
object is used to store the facts and the MemberAccessor
objects. The MemberAccessor
object provides access to the fields/methods of the Dart objects in the expression.
var context = RuleContext(resolve: [
MemberAccessor<Counter>({
'value': (c) => c.value,
'increment': (c) => c.increment,
}),
], facts: {
'counter': Counter(2),
});
The RuleRepository
object is used to store the rules.
The StringRuleRepository
object is a simple implementation of the RuleRepository
interface that stores the rules in a list of strings.
var jsonRules = [
'''
{
"name": "rule1",
"conditions": {
"operator": ">",
"operands": ["age", 18]
},
"actionInfo": {
"onSuccess": {
"operation": "print",
"parameters": ["You are an adult"]
}
}
}
'''
];
var ruleRepository = StringRuleRepository(jsonRules);
The FileRuleRepository
object is an implementation of the RuleRepository
interface that reads the rules from files or directory.
var ruleRepository = FileRuleRepository(
fileNames: [
'rules/rule_one.json',
'rules/rule_two.json',
],
);
Drules supports activation events. An activation event is a signal that triggers the execution of the rules. The activation event contains information about the rule that was activated, the facts that triggered the rule, and the result of the rule.
To listen to the activation events, you can add a listener to the RuleEngine
object.
ruleEngine.addListener((event) {
print(event);
});
Or you can use (+) operator to add a listener.
ruleEngine + print;
Please file feature requests and bugs at the issue tracker.
Drules is released under the Apache License v2.
If you like this package and want to support our opensource work, consider sponsoring us via GitHub Sponsors.
Also consider supporting it by giving a star on GitHub and a like on pub.dev.
If you would like to contribute to this project, please feel free to send a pull request.