-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmatcher.go
More file actions
87 lines (71 loc) · 1.65 KB
/
matcher.go
File metadata and controls
87 lines (71 loc) · 1.65 KB
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
package matcher
import (
"errors"
"fmt"
"reflect"
)
type Clause struct {
Field string `json:"field"`
Operator string `json:"op"`
Value interface{} `json:"value"`
}
type FieldInfo struct {
Index int
Value reflect.Value
Type reflect.Type
}
func Matches(data interface{}, clauses []*Clause) (bool, error) {
v := reflect.ValueOf(data)
if v.Kind() != reflect.Struct {
return false, errors.New("Matching is only permitted against structs")
}
// Return early if matching will be a no-op
if len(clauses) == 0 {
return true, nil
}
referenced := make(map[string]bool)
matchable := make(map[string]*FieldInfo)
for _, clause := range clauses {
referenced[clause.Field] = true
}
st := v.Type()
n := st.NumField()
for i := 0; i < n; i++ {
field := st.Field(i)
if field.PkgPath != "" && !field.Anonymous {
continue
}
tag := field.Tag.Get("matcher")
if tag == "-" || tag == "" {
continue
}
matchable[tag] = &FieldInfo{
Index: i,
Value: v.Field(i),
Type: field.Type,
}
}
for _, clause := range clauses {
fieldInfo, ok := matchable[clause.Field]
if !ok {
return false, fmt.Errorf("\"%s\" is not a matchable field within the struct: %+v", clause.Field, st)
}
cmp, err := GetComparator(fieldInfo.Type)
if err != nil {
return false, err
}
data := fieldInfo.Value.Interface()
if err := cmp.Valid(data); err != nil {
return false, err
} else if err := cmp.Valid(clause.Value); err != nil {
return false, err
}
matched, err := Compare(cmp, clause.Operator, data, clause.Value)
if err != nil {
return false, err
} else if !matched {
return false, nil
}
}
return true, nil
}