-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathparser.go
More file actions
207 lines (186 loc) · 5.46 KB
/
parser.go
File metadata and controls
207 lines (186 loc) · 5.46 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
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package rql
import (
"fmt"
"reflect"
"slices"
"strings"
"time"
)
var validNumberOperations = []string{"eq", "neq", "gt", "lt", "gte", "lte"}
var validStringOperations = []string{"eq", "neq", "like", "in", "notin", "notlike", "empty", "notempty"}
var validBoolOperations = []string{"eq", "neq"}
var validDatetimeOperations = []string{"eq", "neq", "gt", "lt", "gte", "lte"}
const TAG = "rql"
const DATATYPE_NUMBER = "number"
const DATATYPE_DATETIME = "datetime"
const DATATYPE_STRING = "string"
const DATATYPE_BOOL = "bool"
const SORT_ORDER_ASC = "asc"
const SORT_ORDER_DESC = "desc"
var validSortOrder = []string{SORT_ORDER_ASC, SORT_ORDER_DESC}
type Query struct {
Filters []Filter `json:"filters"`
GroupBy []string `json:"group_by"`
Offset int `json:"offset"`
Limit int `json:"limit"`
Search string `json:"search"`
Sort []Sort `json:"sort"`
}
type Filter struct {
Name string `json:"name"`
Operator string `json:"operator"`
dataType string
Value any `json:"value"`
}
type Sort struct {
Name string `json:"name"`
Order string `json:"order"`
}
func ValidateQuery(q *Query, checkStruct interface{}) error {
val := reflect.ValueOf(checkStruct)
// validate filters
for _, filterItem := range q.Filters {
//validate filter key name
filterIdx := searchKeyInsideStruct(filterItem.Name, val)
if filterIdx < 0 {
return fmt.Errorf("'%s' is not a valid filter key", filterItem.Name)
}
structKeyTag := val.Type().Field(filterIdx).Tag.Get(TAG)
// validate filter key data type
allowedDataType := getDataTypeOfField(structKeyTag)
filterItem.dataType = allowedDataType
switch allowedDataType {
case DATATYPE_NUMBER:
err := validateNumberType(filterItem)
if err != nil {
return err
}
case DATATYPE_BOOL:
err := validateBoolType(filterItem)
if err != nil {
return err
}
case DATATYPE_DATETIME:
err := validateDatetimeType(filterItem)
if err != nil {
return err
}
case DATATYPE_STRING:
err := validateStringType(filterItem)
if err != nil {
return err
}
default:
return fmt.Errorf("type '%s' is not recognized", allowedDataType)
}
if !isValidOperator(filterItem) {
return fmt.Errorf("value '%s' for key '%s' is valid string", filterItem.Operator, filterItem.Name)
}
}
err := validateGroupByKeys(q, val)
if err != nil {
return err
}
return validateSortKey(q, val)
}
func validateNumberType(filterItem Filter) error {
// check if the type is any of Golang numeric types
// if not, return error
switch filterItem.Value.(type) {
case uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, int, uint:
return nil
default:
return fmt.Errorf("value %v for key '%s' is not int type", filterItem.Value, filterItem.Name)
}
}
func validateDatetimeType(filterItem Filter) error {
// cast the value to datetime
// if failed, return error
castedVal, ok := filterItem.Value.(string)
if !ok {
return fmt.Errorf("value %s for key '%s' is not a valid ISO datetime string", filterItem.Value, filterItem.Name)
}
_, err := time.Parse(time.RFC3339, castedVal)
if err != nil {
return fmt.Errorf("value %s for key '%s' is not a valid ISO datetime string", filterItem.Value, filterItem.Name)
}
return nil
}
func validateBoolType(filterItem Filter) error {
// cast the value to bool
// if failed, return error
_, ok := filterItem.Value.(bool)
if !ok {
return fmt.Errorf("value %v for key '%s' is not bool type", filterItem.Value, filterItem.Name)
}
return nil
}
func validateStringType(filterItem Filter) error {
// cast the value to string
// if failed, return error
_, ok := filterItem.Value.(string)
if !ok {
return fmt.Errorf("value %s for key '%s' is valid string type", filterItem.Value, filterItem.Name)
}
return nil
}
func searchKeyInsideStruct(keyName string, val reflect.Value) int {
for i := 0; i < val.NumField(); i++ {
if strings.ToLower(val.Type().Field(i).Name) == strings.ToLower(keyName) {
return i
}
}
return -1
}
// parse the tag schema which is of the format
// type=int,min=10,max=200
// to extract type else fallback to string
func getDataTypeOfField(tagString string) string {
res := DATATYPE_STRING
splitted := strings.Split(tagString, ",")
for _, item := range splitted {
kvSplitted := strings.Split(item, "=")
if len(kvSplitted) == 2 {
if kvSplitted[0] == "type" {
return kvSplitted[1]
}
}
}
//fallback to string if type not found in tag value
return res
}
func isValidOperator(filterItem Filter) bool {
switch filterItem.dataType {
case DATATYPE_NUMBER:
return slices.Contains(validNumberOperations, filterItem.Operator)
case DATATYPE_DATETIME:
return slices.Contains(validDatetimeOperations, filterItem.Operator)
case DATATYPE_STRING:
return slices.Contains(validStringOperations, filterItem.Operator)
case DATATYPE_BOOL:
return slices.Contains(validBoolOperations, filterItem.Operator)
default:
return false
}
}
func validateSortKey(q *Query, val reflect.Value) error {
for _, item := range q.Sort {
filterIdx := searchKeyInsideStruct(item.Name, val)
if filterIdx < 0 {
return fmt.Errorf("'%s' is not a valid sort key", item.Name)
}
if !slices.Contains(validSortOrder, item.Order) {
return fmt.Errorf("'%s' is not a valid sort key", item.Name)
}
}
return nil
}
func validateGroupByKeys(q *Query, val reflect.Value) error {
for _, item := range q.GroupBy {
filterIdx := searchKeyInsideStruct(item, val)
if filterIdx < 0 {
return fmt.Errorf("'%s' is not a valid sort key", item)
}
}
return nil
}