A simple rules engine with great flexibility capable of building binary decision trees of any depth.
Utilizes gjson for pathing for extra flexibility.
Inspired by grules
json := `
{
"name": {"first": "anakin", "last": "skywalker"},
"age": 22,
"children": ["luke", "leia"],
"order": "jedi",
"friends": [
{"first": "r2d2", "last": "droid", "order": "republic", "age": 13, "episodes": [1,2,3,4,5,6,7,8,9]},
{"first": "ben", "last": "kenobi", "order": "jedi", "age": 38, "episodes": [1,2,3,4,5,6]},
{"first": "c3po", "last": "droid", "order": "republic", "age": 13, "episodes": [1,2,3,4,5,6,7,8,9]},
{"first": "sheev", "last": "palpatine", "order": "sith", "age": 63, "episodes": [1,2,3,5,6,9]}
]
}
`
rule := `
{
"comparer": "eq",
"path": "name.first",
"value": "anakin"
}
`
pass, failReason := grules.Evaluate(json, rule)
if !pass {
fmt.Println("FAILED: ", failreason)
}
fmt.Println(pass)
Output: true
json := `
{
"name": {"first": "anakin", "last": "skywalker"},
"age": 22,
"children": ["luke", "leia"],
"order": "jedi",
"friends": [
{"first": "r2d2", "last": "droid", "order": "republic", "age": 13, "episodes": [1,2,3,4,5,6,7,8,9]},
{"first": "ben", "last": "kenobi", "order": "jedi", "age": 38, "episodes": [1,2,3,4,5,6]},
{"first": "c3po", "last": "droid", "order": "republic", "age": 13, "episodes": [1,2,3,4,5,6,7,8,9]},
{"first": "sheev", "last": "palpatine", "order": "sith", "age": 63, "episodes": [1,2,3,5,6,9]}
]
}
`
rule := `
{
"comparer": "lt",
"path": "age",
"value": "20"
}
`
passed, failReason := grules.Evaluate(json, rule)
if !passed {
fmt.Println("FAILED: ", failreason)
}
fmt.Println(passed)
Output: FAILED: value '22' at 'age' is not 'less than' rule value '20'
json := `
{
"name": {"first": "anakin", "last": "skywalker"},
"age": 22,
"children": ["luke", "leia"],
"order": "jedi",
"friends": [
{"first": "r2d2", "last": "droid", "order": "republic", "age": 13, "episodes": [1,2,3,4,5,6,7,8,9]},
{"first": "ben", "last": "kenobi", "order": "jedi", "age": 38, "episodes": [1,2,3,4,5,6]},
{"first": "c3po", "last": "droid", "order": "republic", "age": 13, "episodes": [1,2,3,4,5,6,7,8,9]},
{"first": "sheev", "last": "palpatine", "order": "sith", "age": 63, "episodes": [1,2,3,5,6,9]}
]
}
`
rule := `
{
"operator": "or",
"rules": [
{
"operator": "and",
"rules": [
{
"path": "name.first",
"comparer": "eq",
"value": "darth"
},
{
"path": "name.last",
"comparer": "eq",
"value": "vader"
}
]
},
{
"operator": "or",
"rules": [
{
"path": "order",
"comparer": "eq",
"value": "first world order"
},
{
"operator": "or",
"path": "friends.#.order",
"comparer": "contains",
"value": "sith"
}
]
}
]
}
`
pass, failReason := grules.Evaluate(json, rule)
if !pass {
fmt.Println("FAILED: ", failreason)
}
fmt.Println(pass)
Output: true
eq
will return true ifa == b
neq
will return true ifa != b
lt
will return true ifa < b
lte
will return true ifa <= b
gt
will return true ifa > b
gte
will return true ifa >= b
contains
will return true ifa
containsb
ncontains
will return true ifa
does not containb
oneof
will return true ifa
is one ofb
noneof
will return true ifa
is not one ofb
regex
will return true ifa
matchesb
contains
and ncontains
work for substring comparisons as well as item-in-collection comparisons.
When used for item-in-collection comparisons, contains
expects the first argument to be a slice. contains
is different than oneof
in that oneof
expects the second argument to be a slice.
Benchmark | N | Speed | Used | Allocs |
---|---|---|---|---|
BenchmarkEqual-12 | 650602549 | 5.52 ns/op | 0 B/op | 0 allocs/op |
BenchmarkNotEqual-12 | 876894124 | 4.09 ns/op | 0 B/op | 0 allocs/op |
BenchmarkLessThan-12 | 1000000000 | 2.84 ns/op | 0 B/op | 0 allocs/op |
BenchmarkLessThanEqual-12 | 1000000000 | 2.57 ns/op | 0 B/op | 0 allocs/op |
BenchmarkGreaterThan-12 | 1000000000 | 2.07 ns/op | 0 B/op | 0 allocs/op |
BenchmarkGreaterThanEqual-12 | 1000000000 | 2.86 ns/op | 0 B/op | 0 allocs/op |
BenchmarkRegex-12 | 4524237 | 793 ns/op | 753 B/op | 11 allocs/op |
BenchmarkRegexPhone-12 | 1000000 | 3338 ns/op | 3199 B/op | 30 allocs/op |
BenchmarkContains-12 | 499627219 | 7.16 ns/op | 0 B/op | 0 allocs/op |
BenchmarkStringContains-12 | 405497102 | 8.87 ns/op | 0 B/op | 0 allocs/op |
BenchmarkContainsLong50000-12 | 18992 | 184016 ns/op | 0 B/op | 0 allocs/op |
BenchmarkNotContains-12 | 292932907 | 12.3 ns/op | 0 B/op | 0 allocs/op |
BenchmarkStringNotContains-12 | 392618857 | 9.14 ns/op | 0 B/op | 0 allocs/op |
BenchmarkNotContainsLong50000-12 | 19243 | 191787 ns/op | 0 B/op | 0 allocs/op |
BenchmarkOneOf-12 | 1000000000 | 1.80 ns/op | 0 B/op | 0 allocs/op |
BenchmarkNoneOf-12 | 1000000000 | 1.79 ns/op | 0 B/op | 0 allocs/op |
BenchmarkPluckShallow-12 | 85997188 | 41.6 ns/op | 16 B/op | 1 allocs/op |
BenchmarkPluckDeep-12 | 18789103 | 194 ns/op | 112 B/op | 1 allocs/op |
BenchmarkRule_evaluate-12 | 69558996 | 51.1 ns/op | 16 B/op | 1 allocs/op |
BenchmarkComposite_evaluate-12 | 59484760 | 55.7 ns/op | 16 B/op | 1 allocs/op |
BenchmarkEngine_Evaluate-12 | 47892318 | 75.0 ns/op | 16 B/op | 1 allocs/op |
To run benchmarks:
go test -run none -bench . -benchtime 3s -benchmem
All benchmarks were run on:
MacOS High Sierra 2.6Ghz Intel Core i7 16 GB 2400 MHz DDR4