1 package growthbook
2
3 import (
4 "encoding/json"
5 "reflect"
6 "regexp"
7 "strings"
8 )
9
10
11
12 type Condition interface {
13 Eval(attrs Attributes) bool
14 }
15
16
17
18 type orCondition struct {
19 conds []Condition
20 }
21
22
23
24 type norCondition struct {
25 conds []Condition
26 }
27
28
29
30 type andCondition struct {
31 conds []Condition
32 }
33
34
35
36 type notCondition struct {
37 cond Condition
38 }
39
40
41
42 type baseCondition struct {
43
44
45 values map[string]interface{}
46 }
47
48
49 func (cond orCondition) Eval(attrs Attributes) bool {
50 if len(cond.conds) == 0 {
51 return true
52 }
53 for i := range cond.conds {
54 if cond.conds[i].Eval(attrs) {
55 return true
56 }
57 }
58 return false
59 }
60
61
62 func (cond norCondition) Eval(attrs Attributes) bool {
63 or := orCondition{cond.conds}
64 return !or.Eval(attrs)
65 }
66
67
68 func (cond andCondition) Eval(attrs Attributes) bool {
69 for i := range cond.conds {
70 if !cond.conds[i].Eval(attrs) {
71 return false
72 }
73 }
74 return true
75 }
76
77
78 func (cond notCondition) Eval(attrs Attributes) bool {
79 return !cond.cond.Eval(attrs)
80 }
81
82
83
84
85 func (cond baseCondition) Eval(attrs Attributes) bool {
86 for k, v := range cond.values {
87 if !evalConditionValue(v, getPath(attrs, k)) {
88 return false
89 }
90 }
91 return true
92 }
93
94
95 func ParseCondition(data []byte) Condition {
96 topLevel := make(map[string]interface{})
97 err := json.Unmarshal(data, &topLevel)
98 if err != nil {
99 logError("Failed parsing JSON input", "Condition")
100 return nil
101 }
102
103 return BuildCondition(topLevel)
104 }
105
106
107
108 func BuildCondition(cond map[string]interface{}) Condition {
109 if or, ok := cond["$or"]; ok {
110 conds := buildSeq(or)
111 if conds == nil {
112 return nil
113 }
114 return orCondition{conds}
115 }
116
117 if nor, ok := cond["$nor"]; ok {
118 conds := buildSeq(nor)
119 if conds == nil {
120 return nil
121 }
122 return norCondition{conds}
123 }
124
125 if and, ok := cond["$and"]; ok {
126 conds := buildSeq(and)
127 if conds == nil {
128 return nil
129 }
130 return andCondition{conds}
131 }
132
133 if not, ok := cond["$not"]; ok {
134 subcond, ok := not.(map[string]interface{})
135 if !ok {
136 logError("Invalid $not in JSON condition data")
137 return nil
138 }
139 cond := BuildCondition(subcond)
140 if cond == nil {
141 return nil
142 }
143 return notCondition{cond}
144 }
145
146 return baseCondition{cond}
147 }
148
149
150
151
152
153 func getPath(attrs Attributes, path string) interface{} {
154 parts := strings.Split(path, ".")
155 var current interface{}
156 for i, p := range parts {
157 if i == 0 {
158 current = attrs[p]
159 } else {
160 m, ok := current.(map[string]interface{})
161 if !ok {
162 return nil
163 }
164 current = m[p]
165 }
166 }
167 return current
168 }
169
170
171 func buildSeq(seq interface{}) []Condition {
172
173 conds, ok := seq.([]interface{})
174 if !ok {
175 logError("Something wrong in condition sequence")
176 return nil
177 }
178
179 retval := make([]Condition, len(conds))
180 for i := range conds {
181
182 condmap, ok := conds[i].(map[string]interface{})
183 if !ok {
184 logError("Something wrong in condition sequence element")
185 return nil
186 }
187 cond := BuildCondition(condmap)
188 if cond == nil {
189 return nil
190 }
191 retval[i] = cond
192 }
193 return retval
194 }
195
196
197
198
199
200
201 func evalConditionValue(condVal interface{}, attrVal interface{}) bool {
202 condmap, ok := condVal.(map[string]interface{})
203 if ok && isOperatorObject(condmap) {
204 for k, v := range condmap {
205 if !evalOperatorCondition(k, attrVal, v) {
206 return false
207 }
208 }
209 return true
210 }
211
212 return jsEqual(condVal, attrVal)
213 }
214
215
216
217 func isOperatorObject(obj map[string]interface{}) bool {
218 for k := range obj {
219 if !strings.HasPrefix(k, "$") {
220 return false
221 }
222 }
223 return true
224 }
225
226
227
228 func evalOperatorCondition(key string, attrVal interface{}, condVal interface{}) bool {
229 switch key {
230 case "$veq", "$vne", "$vgt", "$vgte", "$vlt", "$vlte":
231 attrstring, attrok := attrVal.(string)
232 condstring, reok := condVal.(string)
233 if !reok || !attrok {
234 return false
235 }
236 return versionCompare(key, attrstring, condstring)
237
238 case "$eq":
239 return jsEqual(attrVal, condVal)
240
241 case "$ne":
242 return !jsEqual(attrVal, condVal)
243
244 case "$lt", "$lte", "$gt", "$gte":
245 return compare(key, attrVal, condVal)
246
247 case "$regex":
248 restring, reok := condVal.(string)
249 attrstring, attrok := attrVal.(string)
250 if !reok || !attrok {
251 return false
252 }
253 re, err := regexp.Compile(restring)
254 if err != nil {
255 return false
256 }
257 return re.MatchString(attrstring)
258
259 case "$in":
260 vals, ok := condVal.([]interface{})
261 if !ok {
262 return false
263 }
264 return elementIn(attrVal, vals)
265
266 case "$nin":
267 vals, ok := condVal.([]interface{})
268 if !ok {
269 return false
270 }
271 return !elementIn(attrVal, vals)
272
273 case "$elemMatch":
274 return elemMatch(attrVal, condVal)
275
276 case "$size":
277 if getType(attrVal) != "array" {
278 return false
279 }
280 return evalConditionValue(condVal, float64(len(attrVal.([]interface{}))))
281
282 case "$all":
283 return evalAll(condVal, attrVal)
284
285 case "$exists":
286 return existsCheck(condVal, attrVal)
287
288 case "$type":
289 return getType(attrVal) == condVal.(string)
290
291 case "$not":
292 return !evalConditionValue(condVal, attrVal)
293
294 default:
295 return false
296 }
297 }
298
299
300 func getType(v interface{}) string {
301 if v == nil {
302 return "null"
303 }
304 switch v.(type) {
305 case string:
306 return "string"
307 case float64:
308 return "number"
309 case bool:
310 return "boolean"
311 case []interface{}:
312 return "array"
313 case map[string]interface{}:
314 return "object"
315 default:
316 return "unknown"
317 }
318 }
319
320
321
322 func versionCompare(comp string, v1 string, v2 string) bool {
323 v1 = paddedVersionString(v1)
324 v2 = paddedVersionString(v2)
325 switch comp {
326 case "$veq":
327 return v1 == v2
328 case "$vne":
329 return v1 != v2
330 case "$vgt":
331 return v1 > v2
332 case "$vgte":
333 return v1 >= v2
334 case "$vlt":
335 return v1 < v2
336 case "$vlte":
337 return v1 <= v2
338 }
339 return false
340 }
341
342
343
344 func compare(comp string, x interface{}, y interface{}) bool {
345 switch x.(type) {
346 case float64:
347 xn := x.(float64)
348 yn, ok := y.(float64)
349 if !ok {
350 logWarn("Types don't match in condition comparison operation")
351 return false
352 }
353 switch comp {
354 case "$lt":
355 return xn < yn
356 case "$lte":
357 return xn <= yn
358 case "$gt":
359 return xn > yn
360 case "$gte":
361 return xn >= yn
362 }
363
364 case string:
365 xs := x.(string)
366 ys, ok := y.(string)
367 if !ok {
368 logWarn("Types don't match in condition comparison operation")
369 return false
370 }
371 switch comp {
372 case "$lt":
373 return xs < ys
374 case "$lte":
375 return xs <= ys
376 case "$gt":
377 return xs > ys
378 case "$gte":
379 return xs >= ys
380 }
381 }
382 return false
383 }
384
385
386
387
388 func elementIn(v interface{}, array []interface{}) bool {
389 otherArray, ok := v.([]interface{})
390 if ok {
391
392 return commonElement(array, otherArray)
393 }
394
395
396 for _, val := range array {
397 if jsEqual(v, val) {
398 return true
399 }
400 }
401 return false
402 }
403
404
405
406 func commonElement(a1 []interface{}, a2 []interface{}) bool {
407 for _, el1 := range a1 {
408 for _, el2 := range a2 {
409 if reflect.DeepEqual(el1, el2) {
410 return true
411 }
412 }
413 }
414 return false
415 }
416
417
418 func elemMatch(attrVal interface{}, condVal interface{}) bool {
419
420
421 attrs, ok := attrVal.([]interface{})
422 if !ok {
423 return false
424 }
425 condmap, ok := condVal.(map[string]interface{})
426 if !ok {
427 return false
428 }
429
430
431 check := func(v interface{}) bool { return evalConditionValue(condVal, v) }
432 if !isOperatorObject(condmap) {
433 cond := BuildCondition(condmap)
434 if cond == nil {
435 return false
436 }
437
438 check = func(v interface{}) bool {
439 vmap, ok := v.(map[string]interface{})
440 if !ok {
441 return false
442 }
443 as := Attributes(vmap)
444 return cond.Eval(as)
445 }
446 }
447
448
449 for _, a := range attrs {
450 if check(a) {
451 return true
452 }
453 }
454 return false
455 }
456
457
458 func existsCheck(condVal interface{}, attrVal interface{}) bool {
459 cond, ok := condVal.(bool)
460 if !ok {
461 return false
462 }
463 if !cond {
464 return attrVal == nil
465 }
466 return attrVal != nil
467 }
468
469
470 func evalAll(condVal interface{}, attrVal interface{}) bool {
471 conds, okc := condVal.([]interface{})
472 attrs, oka := attrVal.([]interface{})
473 if !okc || !oka {
474 return false
475 }
476 for _, c := range conds {
477 passed := false
478 for _, a := range attrs {
479 if evalConditionValue(c, a) {
480 passed = true
481 break
482 }
483 }
484 if !passed {
485 return false
486 }
487 }
488 return true
489 }
490
491
492
493
494
495
496
497
498
499
500
501 func jsEqual(a interface{}, b interface{}) bool {
502 if a == nil {
503 return b == nil
504 }
505 if b == nil {
506 return false
507 }
508 switch reflect.TypeOf(a).Kind() {
509 case reflect.Array, reflect.Slice:
510 aa, aok := a.([]interface{})
511 ba, bok := b.([]interface{})
512 if !aok || !bok {
513 return false
514 }
515 if len(aa) != len(ba) {
516 return false
517 }
518 for i, av := range aa {
519 if !jsEqual(av, ba[i]) {
520 return false
521 }
522 }
523 return true
524
525 case reflect.Map:
526 am, aok := a.(map[string]interface{})
527 bm, bok := b.(map[string]interface{})
528 if !aok || !bok {
529 return false
530 }
531 if len(am) != len(bm) {
532 return false
533 }
534 for k, av := range am {
535 bv, ok := bm[k]
536 if !ok {
537 return false
538 }
539 if !jsEqual(av, bv) {
540 return false
541 }
542 }
543 return true
544
545 default:
546 return reflect.DeepEqual(normalizeNumber(a), normalizeNumber(b))
547 }
548 }
549
550 func normalizeNumber(a interface{}) interface{} {
551 v := reflect.ValueOf(a)
552 if v.CanFloat() {
553 return v.Float()
554 }
555 if v.CanInt() {
556 return float64(v.Int())
557 }
558 if v.CanUint() {
559 return float64(v.Uint())
560 }
561 return a
562 }
563
View as plain text