A lightweight and extensible rules engine for Go, designed to support conditional execution of actions based on user-defined rules.
- Custom Conditions: Define your own conditions for evaluating rules.
- Flexible Actions: Execute actions when conditions are met.
- Execution Modes: Supports
AllMatch
,AnyMatch
, andNoneMatch
execution strategies. - Rule Prioritization: Rules are executed based on their priority.
- Extensible Registry: Centralized registration of conditions and actions.
To use the rules engine in your project, add it as a dependency:
go get github.com/lvm/go-rules
-
Initialize the Rule Engine
The rule engine holds the conditions and actions that the rules engine will use.ruleEngine := NewRuleEngine(context.TODO(), AllMatch, func(msg string) { fmt.Println("Log:", msg) })
-
Create Conditions and Actions
Define conditions and actions to be used in rules.isEven := func(c context.Context, args Arguments) bool { n, _ := args["number"].(int) return n%2 == 0 } printSuccess := func(c context.Context, args Arguments) error { fmt.Println("number is even!") return nil } rule := NewRule(isEven, printSuccess, 1)
-
Add Rules and Execute
Add rules to the engine and execute them.ruleEngine.AddRules(rule) if err := ruleEngine.Execute(Arguments{"number": 4}); err != nil { fmt.Println("Execution failed:", err) }
-
Managing Rule Engines
Use the
Registry
to manage multiple rule engines.registry := NewRegistry() registry.AddEngine("default", *ruleEngine) defaultEngine := registry.GetEngine("default")
- AllMatch: Executes all matching rules.
- AnyMatch: Executes if any rule matches.
- NoneMatch: Executes if no rules match.
Example with different execution modes:
NewRuleEngine(context.TODO(), AllMatch, func(msg string) {})
NewRuleEngine(context.TODO(), AnyMatch, func(msg string) {})
NewRuleEngine(context.TODO(), NoneMatch, func(msg string) {})
Combine multiple conditions using All
, Any
, or None
.
isEven := func(c context.Context, args Arguments) bool { return args["number"].(int)%2 == 0 }
isPositive := func(c context.Context, args Arguments) bool { return args["number"].(int) > 0 }
allConditions := All(isEven, isPositive)
anyConditions := Any(isEven, isPositive)
nonConditions := None(isEven, isPositive)
The rules engine allows you to store and retrieve values from the context, enabling dynamic behavior based on the context during rule evaluation.
-
Setting and Getting Context Values
You can store context values usingSetContext
and retrieve them withGetContext
. This is useful for passing additional data that might influence rule execution. -
Using Context in Conditions and Actions
Rules can use context values to modify their behavior. For instance, a condition might check if a specific context value exists and decide whether to execute an action or skip the rule entirely.
ruleEngine := NewRuleEngine(context.TODO(), AllMatch, func(msg string) {})
isEvenCondition := func(c context.Context, args Arguments) bool {
if c.Value("ForcePass") != nil {
return true
}
n, _ := args["number"].(int)
return n%2 == 0
}
printAction := func(c context.Context, args Arguments) error {
n, _ := args["number"].(int)
fmt.Printf("Success: %d is even!\n", n)
return nil
}
rule := NewRule(isEvenCondition, printAction, 1)
ruleEngine.AddRules(rule)
ruleEngine.Execute(Arguments{"number": 4}) // this should pass: 4 is even.
ruleEngine.SetContext("ForcePass", 1)
ruleEngine.Execute(Arguments{"number": 5}) // this too should pass: even though 5 is odd, ForcePass is not nil
Integrate the rules engine into a Gin application as middleware:
func RulesMiddleware(ruleEngine *RuleEngine) gin.HandlerFunc {
return func(c *gin.Context) {
args := Arguments{
"path": c.Request.URL.Path,
"method": c.Request.Method,
}
if err := ruleEngine.Execute(args); err != nil {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": err.Error(),
})
return
}
c.Next()
}
}
The project includes a suite of tests for validating functionality. To run the tests:
go test -v ./...
Contributions are welcome! Please open an issue or submit a pull request for any bug fixes or feature requests.
See LICENSE.