blob: e7047901a3f6d7c3f6519e382c62d2ce427d6949 [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"
Scott Bakerb40a3bf2020-06-05 10:22:09 -070020 "log"
Zack Williamse940c7a2019-08-21 14:25:39 -070021 "reflect"
22 "regexp"
23 "strings"
24)
25
26type Operation int
27
28const (
29 UK Operation = iota
30 EQ
31 NE
32 GT
33 LT
34 GE
35 LE
36 RE
37)
38
39func toOp(op string) Operation {
40 switch op {
41 case "=":
42 return EQ
43 case "!=":
44 return NE
45 case ">":
46 return GT
47 case "<":
48 return LT
49 case ">=":
50 return GE
51 case "<=":
52 return LE
53 case "~":
54 return RE
55 default:
56 return UK
57 }
58}
59
60type FilterTerm struct {
61 Op Operation
62 Value string
63 re *regexp.Regexp
64}
65
66type Filter map[string]FilterTerm
67
David Bainbridge12f036f2019-10-15 22:09:04 +000068var termRE = regexp.MustCompile(`^\s*([a-zA-Z_][.a-zA-Z0-9_]*)\s*(~|<=|>=|<|>|!=|=)\s*(.+)\s*$`)
Zack Williamse940c7a2019-08-21 14:25:39 -070069
70// Parse parses a comma separated list of filter terms
71func Parse(spec string) (Filter, error) {
72 filter := make(map[string]FilterTerm)
73 terms := strings.Split(spec, ",")
74 var err error
75
76 // Each term is in the form <key><op><value>
77 for _, term := range terms {
78 parts := termRE.FindAllStringSubmatch(term, -1)
79 if parts == nil {
80 return nil, fmt.Errorf("Unable to parse filter term '%s'", term)
81 }
82 ft := FilterTerm{
83 Op: toOp(parts[0][2]),
84 Value: parts[0][3],
85 }
86 if ft.Op == RE {
87 ft.re, err = regexp.Compile(ft.Value)
88 if err != nil {
89 return nil, fmt.Errorf("Unable to parse regexp filter value '%s'", ft.Value)
90 }
91 }
92 filter[parts[0][1]] = ft
93 }
94 return filter, nil
95}
96
97func (f Filter) Process(data interface{}) (interface{}, error) {
98 slice := reflect.ValueOf(data)
99 if slice.Kind() != reflect.Slice {
100 if f.Evaluate(data) {
101 return data, nil
102 }
103 return nil, nil
104 }
105
106 var result []interface{}
107
108 for i := 0; i < slice.Len(); i++ {
109 if f.Evaluate(slice.Index(i).Interface()) {
110 result = append(result, slice.Index(i).Interface())
111 }
112 }
113
114 return result, nil
115}
116
Scott Bakered4efab2020-01-13 19:12:25 -0800117// returns False if the filter does not match
118// returns true if the filter does match or the operation is unsupported
119func testField(v FilterTerm, field reflect.Value) bool {
120 switch v.Op {
121 case RE:
122 if !v.re.MatchString(fmt.Sprintf("%v", field)) {
123 return false
124 }
125 case EQ:
126 // This seems to work for most comparisons
127 if fmt.Sprintf("%v", field) != v.Value {
128 return false
129 }
130 case NE:
131 // This seems to work for most comparisons
132 if fmt.Sprintf("%v", field) == v.Value {
133 return false
134 }
135 default:
136 // For unsupported operations, always pass
137 }
138
139 return true
140}
141
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700142func (f Filter) EvaluateTerm(k string, v FilterTerm, val reflect.Value, recurse bool) bool {
Scott Baker9173ed82020-05-19 08:30:12 -0700143 // If we have been given a pointer, then deference it
144 if val.Kind() == reflect.Ptr {
145 val = reflect.Indirect(val)
146 }
147
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700148 // If the user gave us an explicitly named dotted field, then split it
149 if strings.Contains(k, ".") {
150 parts := strings.SplitN(k, ".", 2)
151 field := val.FieldByName(parts[0])
Zack Williamse940c7a2019-08-21 14:25:39 -0700152 if !field.IsValid() {
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700153 log.Printf("Failed to find dotted field %s while filtering\n", parts[0])
Zack Williamse940c7a2019-08-21 14:25:39 -0700154 return false
155 }
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700156 return f.EvaluateTerm(parts[1], v, field, false)
157 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700158
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700159 field := val.FieldByName(k)
160 if !field.IsValid() {
161 log.Printf("Failed to find field %s while filtering\n", k)
162 return false
163 }
164
165 if (field.Kind() == reflect.Slice) || (field.Kind() == reflect.Array) {
166 // For an array, check to see if any item matches
167 someMatch := false
168 for i := 0; i < field.Len(); i++ {
169 arrayElem := field.Index(i)
170 if testField(v, arrayElem) {
171 someMatch = true
Zack Williamse940c7a2019-08-21 14:25:39 -0700172 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700173 }
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700174 if !someMatch {
175 //if recurse && val.Kind() == reflect.Struct {
176 // TODO: implement automatic recursion when the user did not
177 // use a dotted notation. Go through the list of fields
178 // in the struct, recursively check each one.
179 //}
180 return false
181 }
182 } else {
183 if !testField(v, field) {
184 return false
185 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700186 }
Scott Bakerb40a3bf2020-06-05 10:22:09 -0700187
188 return true
189}
190
191func (f Filter) Evaluate(item interface{}) bool {
192 val := reflect.ValueOf(item)
193
194 for k, v := range f {
195 matches := f.EvaluateTerm(k, v, val, true)
196 if !matches {
197 // If any of the filter fail, the overall match fails
198 return false
199 }
200 }
201
Zack Williamse940c7a2019-08-21 14:25:39 -0700202 return true
203}