-
Notifications
You must be signed in to change notification settings - Fork 1
/
query_filter.go
135 lines (123 loc) · 3.1 KB
/
query_filter.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
package protopath
import (
"context"
"reflect"
)
type operator uint
const (
greater operator = iota
greaterEq
less
lessEq
eq
nEq
or
and
)
// QueryFilterOp is operation to filter given array of object
// LeftOp must be present
// RightOp and Operator is optional
// LeftOp and RightOp possible values:
// 1. List of operation. Needs to evaluate all the operations to get the value
// 2. QueryFilterOp. Operation contains comparation or logical operation
// 3. Exact value, e.g string or float64
type QueryFilterOp struct {
LeftOp any
RightOp any
Operator operator
}
var _ Operation = (*QueryFilterOp)(nil)
// Lookup find the filtered value given object
func (q *QueryFilterOp) Lookup(ctx context.Context, obj, rootObj any) (any, error) {
reflectVal := reflect.ValueOf(obj)
switch reflectVal.Kind() {
case reflect.Slice:
res := make([]any, 0)
for i := 0; i < reflectVal.Len(); i++ {
currVal := reflectVal.Index(i).Interface()
currReflectVal := reflect.ValueOf(currVal)
// if each of entry is also a slice, we need to flatten it
if currReflectVal.Kind() == reflect.Slice {
for j := 0; j < currReflectVal.Len(); j++ {
innerVal := currReflectVal.Index(j).Interface()
meetCriteria, err := q.eval(ctx, innerVal, rootObj)
if err != nil {
return nil, err
}
if meetCriteria {
res = append(res, innerVal)
}
}
continue
}
meetCriteria, err := q.eval(ctx, currVal, rootObj)
if err != nil {
return nil, err
}
if meetCriteria {
res = append(res, currVal)
}
}
return res, nil
default:
meetCriteria, err := q.eval(ctx, obj, rootObj)
if err != nil {
return nil, err
}
if !meetCriteria {
return nil, nil
}
return obj, nil
}
}
func (q *QueryFilterOp) eval(ctx context.Context, obj, rootObj any) (bool, error) {
leftVal, err := q.getValFromAnyOperations(ctx, q.LeftOp, obj, rootObj)
if err != nil {
return false, err
}
// return early when left value is boolean with conditions
// 1. operator is 'or'
// 2. there is no right operation
if boolVal, isBoolVal := leftVal.(bool); isBoolVal {
if q.Operator == or && boolVal == true {
return true, nil
}
if q.RightOp == nil {
return boolVal, nil
}
}
// if there is no right operation but the left value is not in bool type
// it check whether the value is nil
if q.RightOp == nil {
return leftVal != nil, nil
}
rightVal, err := q.getValFromAnyOperations(ctx, q.RightOp, obj, rootObj)
if err != nil {
return false, err
}
switch q.Operator {
case greater, greaterEq, less, lessEq, eq, nEq:
return comparator(leftVal, rightVal, q.Operator), nil
case or, and:
return logicalOperation(leftVal, rightVal, q.Operator), nil
}
return false, nil
}
func (q *QueryFilterOp) getValFromAnyOperations(ctx context.Context, ops, obj, rootObj any) (any, error) {
switch opsT := ops.(type) {
case []Operation:
val := obj
var err error
for _, op := range opsT {
val, err = op.Lookup(ctx, val, rootObj)
if err != nil {
return nil, err
}
}
return val, nil
case *QueryFilterOp:
return opsT.eval(ctx, obj, rootObj)
default:
return opsT, nil
}
}