-
Notifications
You must be signed in to change notification settings - Fork 0
/
condval.go
157 lines (141 loc) · 4.31 KB
/
condval.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Package condval provides functionality to evaluate conditions and return corresponding results.
package condval
import (
"encoding/json"
"fmt"
"github.com/expr-lang/expr"
"os"
)
// Error definitions
var (
ErrNoCond = fmt.Errorf("no condition matched")
ErrCompile = fmt.Errorf("failed to compile expression")
ErrRunCode = fmt.Errorf("failed to run expression")
ErrLoadFile = fmt.Errorf("failed to read file")
ErrParseJson = fmt.Errorf("failed to unmarshal JSON")
ErrSubResult = fmt.Errorf("failed to marshal sub-result")
)
// ConditionValue represents a condition and its corresponding result.
type ConditionValue struct {
ConditionExpr string `json:"condition"`
Result interface{} `json:"result"`
}
// ConditionValueConfig is a slice of ConditionValue.
type ConditionValueConfig []ConditionValue
// GetResult evaluates the conditions in the configuration and returns the result of the first matched condition.
func (cvc ConditionValueConfig) GetResult(parameters map[string]interface{}) (interface{}, error) {
ret, _, err := cvc.GetResultWithTrace(parameters)
return ret, err
}
// GetResultWithTrace evaluates the conditions in the configuration and returns the result of the first matched condition along with the trace of evaluated conditions.
func (cvc ConditionValueConfig) GetResultWithTrace(parameters map[string]interface{}) (interface{}, []int, error) {
trace := make([]int, 0)
tryRunCode := func(code string) (interface{}, error) {
program, err := expr.Compile(code, expr.Env(parameters))
if err != nil {
return nil, ErrCompile
}
result, err := expr.Run(program, parameters)
if err != nil {
return nil, ErrRunCode
}
return result, nil
}
for i, cv := range cvc {
code := cv.ConditionExpr
result, err := tryRunCode(code)
if err != nil {
return nil, nil, err
}
if result.(bool) {
trace = append(trace, i)
if subCvc, ok := cv.Result.(ConditionValueConfig); ok {
subRet, subTrace, subErr := subCvc.GetResultWithTrace(parameters)
if subErr != nil {
return nil, nil, subErr
}
trace = append(trace, subTrace...)
return subRet, trace, nil
} else {
if subExpr, ok := cv.Result.(string); ok {
result, err := tryRunCode(subExpr)
if err == nil {
return result, trace, nil
}
}
return cv.Result, trace, nil
}
}
}
return nil, nil, ErrNoCond
}
// Equal checks if two ConditionValueConfig instances are equal.
func (cvc ConditionValueConfig) Equal(another ConditionValueConfig) bool {
if len(cvc) != len(another) {
fmt.Println("lengths not equal")
return false
}
for i, cv := range cvc {
if cv.ConditionExpr != another[i].ConditionExpr {
fmt.Println("conditions not equal")
return false
}
if v1, ok := cv.Result.(ConditionValueConfig); ok {
v2, ok := another[i].Result.(ConditionValueConfig)
if !ok {
fmt.Println("result types not equal")
return false
}
if !v1.Equal(v2) {
fmt.Println("nested results not equal")
return false
}
} else if cv.Result != another[i].Result {
fmt.Printf("results not equal: %v != %v\n", cv.Result, another[i].Result)
return false
}
}
return true
}
// ParseConditionValueConfigFile reads a JSON file and parses it into a ConditionValueConfig.
func ParseConditionValueConfigFile(path string) (ConditionValueConfig, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, ErrLoadFile
}
return ParseConditionValueConfig(raw)
}
// ParseConditionValueConfig parses a JSON byte slice into a ConditionValueConfig.
func ParseConditionValueConfig(raw []byte) (ConditionValueConfig, error) {
var cvc ConditionValueConfig
rawCvc := make([]struct {
ConditionExpr string `json:"condition"`
Result interface{} `json:"result"`
}, 0)
err := json.Unmarshal(raw, &rawCvc)
if err != nil {
return nil, ErrParseJson
}
for _, cv := range rawCvc {
if val, ok := cv.Result.([]interface{}); ok {
subJson, err := json.Marshal(val)
if err != nil {
return nil, ErrSubResult
}
subRet, err := ParseConditionValueConfig(subJson)
if err != nil {
return nil, ErrSubResult
}
cvc = append(cvc, ConditionValue{
ConditionExpr: cv.ConditionExpr,
Result: subRet,
})
} else {
cvc = append(cvc, ConditionValue{
ConditionExpr: cv.ConditionExpr,
Result: cv.Result,
})
}
}
return cvc, nil
}