blob: 2498fa237f92d9532b3f522653a5af0a41bb84b6 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
2 * Copyright 2019-present Ciena Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package filter
17
18import (
19 "fmt"
20 "reflect"
21 "regexp"
22 "strings"
23)
24
25type Operation int
26
27const (
28 UK Operation = iota
29 EQ
30 NE
31 GT
32 LT
33 GE
34 LE
35 RE
36)
37
38func toOp(op string) Operation {
39 switch op {
40 case "=":
41 return EQ
42 case "!=":
43 return NE
44 case ">":
45 return GT
46 case "<":
47 return LT
48 case ">=":
49 return GE
50 case "<=":
51 return LE
52 case "~":
53 return RE
54 default:
55 return UK
56 }
57}
58
59type FilterTerm struct {
60 Op Operation
61 Value string
62 re *regexp.Regexp
63}
64
65type Filter map[string]FilterTerm
66
David Bainbridge12f036f2019-10-15 22:09:04 +000067var termRE = regexp.MustCompile(`^\s*([a-zA-Z_][.a-zA-Z0-9_]*)\s*(~|<=|>=|<|>|!=|=)\s*(.+)\s*$`)
Zack Williamse940c7a2019-08-21 14:25:39 -070068
69// Parse parses a comma separated list of filter terms
70func Parse(spec string) (Filter, error) {
71 filter := make(map[string]FilterTerm)
72 terms := strings.Split(spec, ",")
73 var err error
74
75 // Each term is in the form <key><op><value>
76 for _, term := range terms {
77 parts := termRE.FindAllStringSubmatch(term, -1)
78 if parts == nil {
79 return nil, fmt.Errorf("Unable to parse filter term '%s'", term)
80 }
81 ft := FilterTerm{
82 Op: toOp(parts[0][2]),
83 Value: parts[0][3],
84 }
85 if ft.Op == RE {
86 ft.re, err = regexp.Compile(ft.Value)
87 if err != nil {
88 return nil, fmt.Errorf("Unable to parse regexp filter value '%s'", ft.Value)
89 }
90 }
91 filter[parts[0][1]] = ft
92 }
93 return filter, nil
94}
95
96func (f Filter) Process(data interface{}) (interface{}, error) {
97 slice := reflect.ValueOf(data)
98 if slice.Kind() != reflect.Slice {
Scott Baker9a2d9a42020-06-09 18:11:26 -070099 match, err := f.Evaluate(data)
100 if err != nil {
101 return nil, err
102 }
103 if match {
Zack Williamse940c7a2019-08-21 14:25:39 -0700104 return data, nil
105 }
106 return nil, nil
107 }
108
109 var result []interface{}
110
111 for i := 0; i < slice.Len(); i++ {
Scott Baker9a2d9a42020-06-09 18:11:26 -0700112 match, err := f.Evaluate(slice.Index(i).Interface())
113 if err != nil {
114 return nil, err
115 }
116 if match {
Zack Williamse940c7a2019-08-21 14:25:39 -0700117 result = append(result, slice.Index(i).Interface())
118 }
119 }
120
121 return result, nil
122}
123
Scott Bakered4efab2020-01-13 19:12:25 -0800124// returns False if the filter does not match
125// returns true if the filter does match or the operation is unsupported
126func testField(v FilterTerm, field reflect.Value) bool {
127 switch v.Op {
128 case RE:
129 if !v.re.MatchString(fmt.Sprintf("%v", field)) {
130 return false
131 }
132 case EQ:
133 // This seems to work for most comparisons
134 if fmt.Sprintf("%v", field) != v.Value {
135 return false
136 }
137 case NE:
138 // This seems to work for most comparisons
139 if fmt.Sprintf("%v", field) == v.Value {
140 return false
141 }
142 default:
143 // For unsupported operations, always pass
144 }
145
146 return true
147}
148
Scott Baker9a2d9a42020-06-09 18:11:26 -0700149func (f Filter) EvaluateTerm(k string, v FilterTerm, val reflect.Value, recurse bool) (bool, error) {
Scott Baker9173ed82020-05-19 08:30:12 -0700150 // If we have been given a pointer, then deference it
151 if val.Kind() == reflect.Ptr {
152 val = reflect.Indirect(val)
153 }
154
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700155 // If the user gave us an explicitly named dotted field, then split it
156 if strings.Contains(k, ".") {
157 parts := strings.SplitN(k, ".", 2)
Scott Baker9a2d9a42020-06-09 18:11:26 -0700158 if val.Kind() != reflect.Struct {
159 return false, fmt.Errorf("Dotted field name specified in filter did not resolve to a valid field")
160 }
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700161 field := val.FieldByName(parts[0])
Zack Williamse940c7a2019-08-21 14:25:39 -0700162 if !field.IsValid() {
Scott Baker9a2d9a42020-06-09 18:11:26 -0700163 return false, fmt.Errorf("Failed to find dotted field %s while filtering", parts[0])
Zack Williamse940c7a2019-08-21 14:25:39 -0700164 }
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700165 return f.EvaluateTerm(parts[1], v, field, false)
166 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700167
Scott Baker9a2d9a42020-06-09 18:11:26 -0700168 if val.Kind() != reflect.Struct {
169 return false, fmt.Errorf("Dotted field name specified in filter did not resolve to a valid field")
170 }
171
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700172 field := val.FieldByName(k)
173 if !field.IsValid() {
Scott Baker9a2d9a42020-06-09 18:11:26 -0700174 return false, fmt.Errorf("Failed to find field %s while filtering", k)
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700175 }
176
177 if (field.Kind() == reflect.Slice) || (field.Kind() == reflect.Array) {
178 // For an array, check to see if any item matches
179 someMatch := false
180 for i := 0; i < field.Len(); i++ {
181 arrayElem := field.Index(i)
182 if testField(v, arrayElem) {
183 someMatch = true
Zack Williamse940c7a2019-08-21 14:25:39 -0700184 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700185 }
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700186 if !someMatch {
187 //if recurse && val.Kind() == reflect.Struct {
188 // TODO: implement automatic recursion when the user did not
189 // use a dotted notation. Go through the list of fields
190 // in the struct, recursively check each one.
191 //}
Scott Baker9a2d9a42020-06-09 18:11:26 -0700192 return false, nil
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700193 }
194 } else {
195 if !testField(v, field) {
Scott Baker9a2d9a42020-06-09 18:11:26 -0700196 return false, nil
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700197 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700198 }
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700199
Scott Baker9a2d9a42020-06-09 18:11:26 -0700200 return true, nil
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700201}
202
Scott Baker9a2d9a42020-06-09 18:11:26 -0700203func (f Filter) Evaluate(item interface{}) (bool, error) {
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700204 val := reflect.ValueOf(item)
205
206 for k, v := range f {
Scott Baker9a2d9a42020-06-09 18:11:26 -0700207 matches, err := f.EvaluateTerm(k, v, val, true)
208 if err != nil {
209 return false, err
210 }
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700211 if !matches {
212 // If any of the filter fail, the overall match fails
Scott Baker9a2d9a42020-06-09 18:11:26 -0700213 return false, nil
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700214 }
215 }
216
Scott Baker9a2d9a42020-06-09 18:11:26 -0700217 return true, nil
Zack Williamse940c7a2019-08-21 14:25:39 -0700218}