-
Notifications
You must be signed in to change notification settings - Fork 3
/
rules.go
157 lines (134 loc) · 4.58 KB
/
rules.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 eppoclient
import (
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
semver "github.com/Masterminds/semver/v3"
)
type condition struct {
Attribute string `json:"attribute"`
Value interface{} `json:"value"`
Operator string `validator:"regexp=^(MATCHES|GTE|GT|LTE|LT|ONE_OF|NOT_ONE_OF)$" json:"operator"`
}
type rule struct {
AllocationKey string `json:"allocationKey"`
Conditions []condition `json:"conditions"`
}
func findMatchingRule(subjectAttributes dictionary, rules []rule) (rule, error) {
for _, rule := range rules {
if matchesRule(subjectAttributes, rule) {
return rule, nil
}
}
return rule{}, errors.New("no matching rule")
}
func matchesRule(subjectAttributes dictionary, rule rule) bool {
for _, condition := range rule.Conditions {
if !evaluateCondition(subjectAttributes, condition) {
return false
}
}
return true
}
func evaluateCondition(subjectAttributes dictionary, condition condition) bool {
subjectValue, exists := subjectAttributes[condition.Attribute]
if !exists {
return false
}
switch condition.Operator {
case "MATCHES":
v := reflect.ValueOf(subjectValue)
if v.Kind() != reflect.String {
subjectValue = strconv.Itoa(subjectValue.(int))
}
r, _ := regexp.MatchString(condition.Value.(string), subjectValue.(string))
return r
case "ONE_OF":
return isOneOf(subjectValue, convertToStringArray(condition.Value))
case "NOT_ONE_OF":
return isNotOneOf(subjectValue, convertToStringArray(condition.Value))
default:
// Attempt to coerce both values to float64 and compare them.
subjectValueNumeric, isNumericSubjectErr := ToFloat64(subjectValue)
conditionValueNumeric, isNumericConditionErr := ToFloat64(condition.Value)
if isNumericSubjectErr == nil && isNumericConditionErr == nil {
return evaluateNumericCondition(subjectValueNumeric, conditionValueNumeric, condition)
}
// Attempt to compare using semantic versioning if both values are strings.
subjectValueStr, isStringSubject := subjectValue.(string)
conditionValueStr, isStringCondition := condition.Value.(string)
if isStringSubject && isStringCondition {
// Attempt to parse both values as semantic versions.
subjectSemVer, errSubject := semver.NewVersion(subjectValueStr)
conditionSemVer, errCondition := semver.NewVersion(conditionValueStr)
// If parsing succeeds, evaluate the semver condition.
if errSubject == nil && errCondition == nil {
return evaluateSemVerCondition(subjectSemVer, conditionSemVer, condition)
}
}
// Fallback logic if neither numeric nor semver comparison is applicable.
return false
}
}
func convertToStringArray(conditionValue interface{}) []string {
if reflect.TypeOf(conditionValue).Elem().Kind() == reflect.String {
return conditionValue.([]string)
}
conditionValueStrings := make([]string, len(conditionValue.([]interface{})))
for i, v := range conditionValue.([]interface{}) {
conditionValueStrings[i] = v.(string)
}
return conditionValueStrings
}
func isOneOf(attributeValue interface{}, conditionValue []string) bool {
matches := getMatchingStringValues(attributeValue, conditionValue)
return len(matches) > 0
}
func isNotOneOf(attributeValue interface{}, conditionValue []string) bool {
matches := getMatchingStringValues(attributeValue, conditionValue)
return len(matches) == 0
}
func getMatchingStringValues(attributeValue interface{}, conditionValue []string) []string {
v := reflect.ValueOf(attributeValue)
if v.Kind() != reflect.String {
attributeValue = fmt.Sprintf("%v", attributeValue)
}
var result []string
for _, value := range conditionValue {
if strings.EqualFold(value, attributeValue.(string)) {
result = append(result, value)
}
}
return result
}
func evaluateSemVerCondition(subjectValue *semver.Version, conditionValue *semver.Version, condition condition) bool {
switch condition.Operator {
case "GT":
return subjectValue.GreaterThan(conditionValue)
case "GTE":
return subjectValue.GreaterThan(conditionValue) || subjectValue.Equal(conditionValue)
case "LT":
return subjectValue.LessThan(conditionValue)
case "LTE":
return subjectValue.LessThan(conditionValue) || subjectValue.Equal(conditionValue)
default:
panic("Incorrect condition operator")
}
}
func evaluateNumericCondition(subjectValue float64, conditionValue float64, condition condition) bool {
switch condition.Operator {
case "GT":
return subjectValue > conditionValue
case "GTE":
return subjectValue >= conditionValue
case "LT":
return subjectValue < conditionValue
case "LTE":
return subjectValue <= conditionValue
default:
panic("Incorrect condition operator")
}
}