Skip to content

Commit 2762434

Browse files
committed
initial commit
1 parent 3d64e77 commit 2762434

15 files changed

+406
-0
lines changed

README.md

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# JSON Rules
2+
3+
The JSON Rules serves as an abstraction layer over the [Golang Rules Engine](https://github.com/nikunjy/rules/blob/master/README.md).
4+
5+
This packages allows you to represent rules in JSON format instead of using the original ANTLR query syntax from the nikunjy/rules implementation.
6+
7+
## Example Queries
8+
9+
You can find example queries in the [examples/](test/examples/) directory.
10+
11+
## Operations
12+
13+
The following operations are supported, matching those available in the [Golang Rules Engine](https://github.com/nikunjy/rules):
14+
15+
| expression | meaning |
16+
| ---------- | ----------------------------------------------- |
17+
| eq | equals to |
18+
| == | equals to |
19+
| ne | not equal to |
20+
| != | not equal to |
21+
| lt | less than |
22+
| < | less than |
23+
| gt | greater than |
24+
| > | greater than |
25+
| le | less than or equal to |
26+
| <= | less than or equal to |
27+
| ge | greater than or equal to |
28+
| >= | greater than or equal to |
29+
| co | contains |
30+
| sw | starts with |
31+
| ew | ends with |
32+
| in | in a list |
33+
| pr | present, will be true if you have a key as true |
34+
| not | not of a logical expression |
35+
36+
## JSON Rule Example
37+
38+
To create JSON rules, follow these steps:
39+
40+
1. The `and` and `or`operation accepts a list of conditions. The and operation evaluates to true only if all conditions in the list are true, while the or operation evaluates to true if at least one condition is true. They are formatted as follows:
41+
42+
```json
43+
{
44+
"and": [
45+
{ "==": [{ "var": "y" }, 4] },
46+
{ ">": [{ "var": "x" }, 1] }
47+
]
48+
}
49+
```
50+
51+
```json
52+
{
53+
"or": [
54+
{ "==": [{ "var": "y" }, 4] },
55+
{ ">": [{ "var": "x" }, 1] },
56+
{ ">": [{ "var": "x" }, 1] }
57+
]
58+
}
59+
```
60+
61+
2. Other operations, such as `eq`, `ne`, `lt`, `gt`, `le`, `ge`, `in`, and others, are formatted to take exactly two operands. Here’s an example of the `eq` operation:
62+
63+
```json
64+
{
65+
"eq": [{ "var": "x" }, 1]
66+
}
67+
```
68+
69+
## How to use
70+
This example demonstrates how to initialize the parser with a JSON rule and evaluate it against a set of data. Adjust the paths and data as necessary for your specific use case.
71+
72+
```Go
73+
p := parser.NewParser(filepath.Join("examples", "example.json"))
74+
err := p.ParseRule()
75+
if err != nil {
76+
t.Errorf("%v", err)
77+
}
78+
testData := map[string]interface{}{
79+
"x" : 1,
80+
}
81+
result := p.Evaluate(testData)
82+
```

go.mod

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module github.com/ahuangg/json_rules
2+
3+
go 1.23.3
4+
5+
require (
6+
github.com/nikunjy/rules v1.5.0
7+
github.com/stretchr/testify v1.10.0
8+
)
9+
10+
require (
11+
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
12+
github.com/blang/semver v3.5.1+incompatible // indirect
13+
github.com/davecgh/go-spew v1.1.1 // indirect
14+
github.com/pmezard/go-difflib v1.0.0 // indirect
15+
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
16+
gopkg.in/yaml.v3 v3.0.1 // indirect
17+
)

go.sum

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
2+
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
3+
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
4+
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
5+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/nikunjy/rules v1.5.0 h1:KJDSLOsFhwt7kcXUyZqwkgrQg5YoUwj+TVu6ItCQShw=
8+
github.com/nikunjy/rules v1.5.0/go.mod h1:TlZtZdBChrkqi8Lr2AXocme8Z7EsbxtFdDoKeI6neBQ=
9+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
10+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
11+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
12+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
13+
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
14+
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
15+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
16+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
17+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
18+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

parser/parser.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package parser
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
"github.com/nikunjy/rules/parser"
10+
)
11+
12+
type Parser struct {
13+
filePath string
14+
rule string
15+
}
16+
17+
func NewParser(filePath string) *Parser {
18+
return &Parser{
19+
filePath: filePath,
20+
}
21+
}
22+
23+
func (p *Parser) ParseRule() error {
24+
fileData, err := os.ReadFile(p.filePath)
25+
if err != nil {
26+
return err
27+
}
28+
29+
var ruleData map[string]interface{}
30+
if err := json.Unmarshal(fileData, &ruleData); err != nil {
31+
return err
32+
}
33+
34+
p.rule = p.formatRule(ruleData)
35+
36+
return nil
37+
}
38+
39+
func (p *Parser) formatRule(node map[string]interface{}) string {
40+
for _, key := range []string{"and", "or"} {
41+
if rulesList, ok := node[key].([]interface{}); ok {
42+
rules := make([]string, len(rulesList))
43+
for i, r := range rulesList {
44+
rules[i] = p.formatRule(r.(map[string]interface{}))
45+
}
46+
return "(" + strings.Join(rules, " "+key+" ") + ")"
47+
}
48+
}
49+
50+
for op, value := range node {
51+
values := value.([]interface{})
52+
left := values[0]
53+
if m, ok := left.(map[string]interface{}); ok {
54+
left = m["var"]
55+
}
56+
return "(" + fmt.Sprintf("%v %s %v", left, op, values[1]) + ")"
57+
}
58+
59+
return ""
60+
}
61+
62+
func (p *Parser) Evaluate(items map[string]interface{}) bool {
63+
return parser.Evaluate(p.rule, items)
64+
}
65+
66+
func (p *Parser) GetRule() string {
67+
return p.rule
68+
}

test/examples/eq2_test.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"==": [{ "var": "x" }, 2]
3+
}

test/examples/eq_and_gt_test.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"and": [
3+
{"==": [{"var": "y"}, 4]},
4+
{">": [{"var": "x"}, 1]}
5+
]
6+
}

test/examples/eq_and_lte_test.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"and": [
3+
{"==": [{"var": "x.a"}, 1]},
4+
{"<=": [{"var": "x.b.c"}, 2]}
5+
]
6+
}

test/examples/eq_string_test.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"and": [
3+
{"==": [{"var": "y"}, 4]},
4+
{"eq": [{"var": "x"}, "1.2.3"]}
5+
]
6+
}

test/examples/eq_test.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"eq": [{ "var": "x" }, 1]
3+
}

test/examples/gt2_test.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
">": [{ "var": "x" }, 2]
3+
}

test/examples/gt_test.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
">": [{ "var": "x" }, 0]
3+
}

test/examples/in_test.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"and": [
3+
{"==": [{"var": "y"}, 4]},
4+
{"in": [{"var": "x"}, [1, 2, 3]]}
5+
]
6+
}

test/examples/lt2_test.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"<": [{"var": "x"}, 1]
3+
}

test/examples/lt_test.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"<": [{ "var": "x" }, 2]
3+
}

0 commit comments

Comments
 (0)