1 package gval
2
3 import (
4 "context"
5 "fmt"
6 "regexp"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/shopspring/decimal"
12 )
13
14 func TestParameterized(t *testing.T) {
15 testEvaluate(
16 []evaluationTest{
17 {
18 name: "Single parameter modified by constant",
19 expression: "foo + 2",
20 parameter: map[string]interface{}{
21 "foo": 2.0,
22 },
23 want: 4.0,
24 },
25 {
26
27 name: "Single parameter modified by variable",
28 expression: "foo * bar",
29 parameter: map[string]interface{}{
30 "foo": 5.0,
31 "bar": 2.0,
32 },
33 want: 10.0,
34 },
35 {
36
37 name: "Single parameter modified by variable",
38 expression: `foo["hey"] * bar[1]`,
39 parameter: map[string]interface{}{
40 "foo": map[string]interface{}{"hey": 5.0},
41 "bar": []interface{}{7., 2.0},
42 },
43 want: 10.0,
44 },
45 {
46
47 name: "Multiple multiplications of the same parameter",
48 expression: "foo * foo * foo",
49 parameter: map[string]interface{}{
50 "foo": 10.0,
51 },
52 want: 1000.0,
53 },
54 {
55
56 name: "Multiple additions of the same parameter",
57 expression: "foo + foo + foo",
58 parameter: map[string]interface{}{
59 "foo": 10.0,
60 },
61 want: 30.0,
62 },
63 {
64 name: "NoSpaceOperator",
65 expression: "true&&name",
66 parameter: map[string]interface{}{
67 "name": true,
68 },
69 want: true,
70 },
71 {
72
73 name: "Parameter name sensitivity",
74 expression: "foo + FoO + FOO",
75 parameter: map[string]interface{}{
76 "foo": 8.0,
77 "FoO": 4.0,
78 "FOO": 2.0,
79 },
80 want: 14.0,
81 },
82 {
83
84 name: "Sign prefix comparison against prefixed variable",
85 expression: "-1 < -foo",
86 parameter: map[string]interface{}{"foo": -8.0},
87 want: true,
88 },
89 {
90
91 name: "Fixed-point parameter",
92 expression: "foo > 1",
93 parameter: map[string]interface{}{"foo": 2},
94 want: true,
95 },
96 {
97
98 name: "Modifier after closing clause",
99 expression: "(2 + 2) + 2 == 6",
100 want: true,
101 },
102 {
103
104 name: "Comparator after closing clause",
105 expression: "(2 + 2) >= 4",
106 want: true,
107 },
108 {
109
110 name: "Two-boolean logical operation (for issue #8)",
111 expression: "(foo == true) || (bar == true)",
112 parameter: map[string]interface{}{
113 "foo": true,
114 "bar": false,
115 },
116 want: true,
117 },
118 {
119
120 name: "Two-variable integer logical operation (for issue #8)",
121 expression: "foo > 10 && bar > 10",
122 parameter: map[string]interface{}{
123 "foo": 1,
124 "bar": 11,
125 },
126 want: false,
127 },
128 {
129
130 name: "Regex against right-hand parameter",
131 expression: `"foobar" =~ foo`,
132 parameter: map[string]interface{}{
133 "foo": "obar",
134 },
135 want: true,
136 },
137 {
138
139 name: "Not-regex against right-hand parameter",
140 expression: `"foobar" !~ foo`,
141 parameter: map[string]interface{}{
142 "foo": "baz",
143 },
144 want: true,
145 },
146 {
147
148 name: "Regex against two parameter",
149 expression: `foo =~ bar`,
150 parameter: map[string]interface{}{
151 "foo": "foobar",
152 "bar": "oba",
153 },
154 want: true,
155 },
156 {
157
158 name: "Not-regex against two parameter",
159 expression: "foo !~ bar",
160 parameter: map[string]interface{}{
161 "foo": "foobar",
162 "bar": "baz",
163 },
164 want: true,
165 },
166 {
167
168 name: "Pre-compiled regex",
169 expression: "foo =~ bar",
170 parameter: map[string]interface{}{
171 "foo": "foobar",
172 "bar": regexp.MustCompile("[fF][oO]+"),
173 },
174 want: true,
175 },
176 {
177
178 name: "Pre-compiled not-regex",
179 expression: "foo !~ bar",
180 parameter: map[string]interface{}{
181 "foo": "foobar",
182 "bar": regexp.MustCompile("[fF][oO]+"),
183 },
184 want: false,
185 },
186 {
187
188 name: "Single boolean parameter",
189 expression: "commission ? 10",
190 parameter: map[string]interface{}{
191 "commission": true},
192 want: 10.0,
193 },
194 {
195
196 name: "True comparator with a parameter",
197 expression: `partner == "amazon" ? 10`,
198 parameter: map[string]interface{}{
199 "partner": "amazon"},
200 want: 10.0,
201 },
202 {
203
204 name: "False comparator with a parameter",
205 expression: `partner == "amazon" ? 10`,
206 parameter: map[string]interface{}{
207 "partner": "ebay"},
208 want: nil,
209 },
210 {
211
212 name: "True comparator with multiple parameters",
213 expression: "theft && period == 24 ? 60",
214 parameter: map[string]interface{}{
215 "theft": true,
216 "period": 24,
217 },
218 want: 60.0,
219 },
220 {
221
222 name: "False comparator with multiple parameters",
223 expression: "theft && period == 24 ? 60",
224 parameter: map[string]interface{}{
225 "theft": false,
226 "period": 24,
227 },
228 want: nil,
229 },
230 {
231
232 name: "String concat with single string parameter",
233 expression: `foo + "bar"`,
234 parameter: map[string]interface{}{
235 "foo": "baz"},
236 want: "bazbar",
237 },
238 {
239
240 name: "String concat with multiple string parameter",
241 expression: "foo + bar",
242 parameter: map[string]interface{}{
243 "foo": "baz",
244 "bar": "quux",
245 },
246 want: "bazquux",
247 },
248 {
249
250 name: "String concat with float parameter",
251 expression: "foo + bar",
252 parameter: map[string]interface{}{
253 "foo": "baz",
254 "bar": 123.0,
255 },
256 want: "baz123",
257 },
258 {
259
260 name: "Mixed multiple string concat",
261 expression: `foo + 123 + "bar" + true`,
262 parameter: map[string]interface{}{"foo": "baz"},
263 want: "baz123bartrue",
264 },
265 {
266
267 name: "Integer width spectrum",
268 expression: "uint8 + uint16 + uint32 + uint64 + int8 + int16 + int32 + int64",
269 parameter: map[string]interface{}{
270 "uint8": uint8(0),
271 "uint16": uint16(0),
272 "uint32": uint32(0),
273 "uint64": uint64(0),
274 "int8": int8(0),
275 "int16": int16(0),
276 "int32": int32(0),
277 "int64": int64(0),
278 },
279 want: 0.0,
280 },
281 {
282
283 name: "Null coalesce right",
284 expression: "foo ?? 1.0",
285 parameter: map[string]interface{}{"foo": nil},
286 want: 1.0,
287 },
288 {
289
290 name: "Multiple comparator/logical operators (#30)",
291 expression: "(foo >= 2887057408 && foo <= 2887122943) || (foo >= 168100864 && foo <= 168118271)",
292 parameter: map[string]interface{}{"foo": 2887057409},
293 want: true,
294 },
295 {
296
297 name: "Multiple comparator/logical operators, opposite order (#30)",
298 expression: "(foo >= 168100864 && foo <= 168118271) || (foo >= 2887057408 && foo <= 2887122943)",
299 parameter: map[string]interface{}{"foo": 2887057409},
300 want: true,
301 },
302 {
303
304 name: "Multiple comparator/logical operators, small value (#30)",
305 expression: "(foo >= 2887057408 && foo <= 2887122943) || (foo >= 168100864 && foo <= 168118271)",
306 parameter: map[string]interface{}{"foo": 168100865},
307 want: true,
308 },
309 {
310
311 name: "Multiple comparator/logical operators, small value, opposite order (#30)",
312 expression: "(foo >= 168100864 && foo <= 168118271) || (foo >= 2887057408 && foo <= 2887122943)",
313 parameter: map[string]interface{}{"foo": 168100865},
314 want: true,
315 },
316 {
317
318 name: "Incomparable array equality comparison",
319 expression: "arr == arr",
320 parameter: map[string]interface{}{"arr": []int{0, 0, 0}},
321 want: true,
322 },
323 {
324
325 name: "Incomparable array not-equality comparison",
326 expression: "arr != arr",
327 parameter: map[string]interface{}{"arr": []int{0, 0, 0}},
328 want: false,
329 },
330 {
331
332 name: "Mixed function and parameters",
333 expression: "sum(1.2, amount) + name",
334 extension: Function("sum", func(arguments ...interface{}) (interface{}, error) {
335 sum := 0.0
336 for _, v := range arguments {
337 sum += v.(float64)
338 }
339 return sum, nil
340 },
341 ),
342 parameter: map[string]interface{}{"amount": .8,
343 "name": "awesome",
344 },
345
346 want: "2awesome",
347 },
348 {
349
350 name: "Short-circuit OR",
351 expression: "true || fail()",
352 extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
353 return nil, fmt.Errorf("Did not short-circuit")
354 }),
355 want: true,
356 },
357 {
358
359 name: "Short-circuit AND",
360 expression: "false && fail()",
361 extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
362 return nil, fmt.Errorf("Did not short-circuit")
363 }),
364 want: false,
365 },
366 {
367
368 name: "Short-circuit ternary",
369 expression: "true ? 1 : fail()",
370 extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
371 return nil, fmt.Errorf("Did not short-circuit")
372 }),
373 want: 1.0,
374 },
375 {
376
377 name: "Short-circuit coalesce",
378 expression: `"foo" ?? fail()`,
379 extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
380 return nil, fmt.Errorf("Did not short-circuit")
381 }),
382 want: "foo",
383 },
384 {
385
386 name: "Simple parameter call",
387 expression: "foo.String",
388 parameter: map[string]interface{}{"foo": foo},
389 want: foo.String,
390 },
391 {
392
393 name: "Simple parameter function call",
394 expression: "foo.Func()",
395 parameter: map[string]interface{}{"foo": foo},
396 want: "funk",
397 },
398 {
399
400 name: "Simple parameter call from pointer",
401 expression: "fooptr.String",
402 parameter: map[string]interface{}{"fooptr": &foo},
403 want: foo.String,
404 },
405 {
406
407 name: "Simple parameter function call from pointer",
408 expression: "fooptr.Func()",
409 parameter: map[string]interface{}{"fooptr": &foo},
410 want: "funk",
411 },
412 {
413
414 name: "Simple parameter call",
415 expression: `foo.String == "hi"`,
416 parameter: map[string]interface{}{"foo": foo},
417 want: false,
418 },
419 {
420
421 name: "Simple parameter call with modifier",
422 expression: `foo.String + "hi"`,
423 parameter: map[string]interface{}{"foo": foo},
424 want: foo.String + "hi",
425 },
426 {
427
428 name: "Simple parameter function call, two-arg return",
429 expression: `foo.Func2()`,
430 parameter: map[string]interface{}{"foo": foo},
431 want: "frink",
432 },
433 {
434
435 name: "Simple parameter function call, one arg",
436 expression: `foo.FuncArgStr("boop")`,
437 parameter: map[string]interface{}{"foo": foo},
438 want: "boop",
439 },
440 {
441
442 name: "Simple parameter function call, one arg",
443 expression: `foo.FuncArgStr("boop") + "hi"`,
444 parameter: map[string]interface{}{"foo": foo},
445 want: "boophi",
446 },
447 {
448
449 name: "Nested parameter function call",
450 expression: `foo.Nested.Dunk("boop")`,
451 parameter: map[string]interface{}{"foo": foo},
452 want: "boopdunk",
453 },
454 {
455
456 name: "Nested parameter call",
457 expression: "foo.Nested.Funk",
458 parameter: map[string]interface{}{"foo": foo},
459 want: "funkalicious",
460 },
461 {
462 name: "Nested map call",
463 expression: `foo.Nested.Map["a"]`,
464 parameter: map[string]interface{}{"foo": foo},
465 want: 1,
466 },
467 {
468 name: "Nested slice call",
469 expression: `foo.Nested.Slice[1]`,
470 parameter: map[string]interface{}{"foo": foo},
471 want: 2,
472 },
473 {
474
475 name: "Parameter call with + modifier",
476 expression: "1 + foo.Int",
477 parameter: map[string]interface{}{"foo": foo},
478 want: 102.0,
479 },
480 {
481
482 name: "Parameter string call with + modifier",
483 expression: `"woop" + (foo.String)`,
484 parameter: map[string]interface{}{"foo": foo},
485 want: "woopstring!",
486 },
487 {
488
489 name: "Parameter call with && operator",
490 expression: "true && foo.BoolFalse",
491 parameter: map[string]interface{}{"foo": foo},
492 want: false,
493 },
494 {
495 name: "Null coalesce nested parameter",
496 expression: "foo.Nil ?? false",
497 parameter: map[string]interface{}{"foo": foo},
498 want: false,
499 },
500 {
501 name: "input functions",
502 expression: "func1() + func2()",
503 parameter: map[string]interface{}{
504 "func1": func() float64 { return 2000 },
505 "func2": func() float64 { return 2001 },
506 },
507 want: 4001.0,
508 },
509 {
510 name: "input functions",
511 expression: "func1(date1) + func2(date2)",
512 parameter: map[string]interface{}{
513 "date1": func() interface{} {
514 y2k, _ := time.Parse("2006", "2000")
515 return y2k
516 }(),
517 "date2": func() interface{} {
518 y2k1, _ := time.Parse("2006", "2001")
519 return y2k1
520 }(),
521 },
522 extension: NewLanguage(
523 Function("func1", func(arguments ...interface{}) (interface{}, error) {
524 return float64(arguments[0].(time.Time).Year()), nil
525 }),
526 Function("func2", func(arguments ...interface{}) (interface{}, error) {
527 return float64(arguments[0].(time.Time).Year()), nil
528 }),
529 ),
530 want: 4001.0,
531 },
532 {
533 name: "complex64 number as parameter",
534 expression: "complex64",
535 parameter: map[string]interface{}{
536 "complex64": complex64(0),
537 "complex128": complex128(0),
538 },
539 want: complex64(0),
540 },
541 {
542 name: "complex128 number as parameter",
543 expression: "complex128",
544 parameter: map[string]interface{}{
545 "complex64": complex64(0),
546 "complex128": complex128(0),
547 },
548 want: complex128(0),
549 },
550 {
551 name: "coalesce with undefined",
552 expression: "fooz ?? foo",
553 parameter: map[string]interface{}{
554 "foo": "bar",
555 },
556 want: "bar",
557 },
558 {
559 name: "map[interface{}]interface{}",
560 expression: "foo",
561 parameter: map[interface{}]interface{}{
562 "foo": "bar",
563 },
564 want: "bar",
565 },
566 {
567 name: "method on pointer type",
568 expression: "foo.PointerFunc()",
569 parameter: map[string]interface{}{
570 "foo": &dummyParameter{},
571 },
572 want: "point",
573 },
574 {
575 name: "custom selector",
576 expression: "hello.world",
577 parameter: "!",
578 extension: NewLanguage(Base(), VariableSelector(func(path Evaluables) Evaluable {
579 return func(c context.Context, v interface{}) (interface{}, error) {
580 keys, err := path.EvalStrings(c, v)
581 if err != nil {
582 return nil, err
583 }
584 return fmt.Sprintf("%s%s", strings.Join(keys, " "), v), nil
585 }
586 })),
587 want: "hello world!",
588 },
589 {
590 name: "map[int]int",
591 expression: `a[0] + a[2]`,
592 parameter: map[string]interface{}{
593 "a": map[int]int{0: 1, 2: 1},
594 },
595 want: 2.,
596 },
597 {
598 name: "map[int]string",
599 expression: `a[0] * a[2]`,
600 parameter: map[string]interface{}{
601 "a": map[int]string{0: "1", 2: "1"},
602 },
603 want: 1.,
604 },
605 {
606 name: "coalesce typed nil 0",
607 expression: `ProjectID ?? 0`,
608 parameter: struct {
609 ProjectID *uint
610 }{},
611 want: 0.,
612 },
613 {
614 name: "coalesce typed nil 99",
615 expression: `ProjectID ?? 99`,
616 parameter: struct {
617 ProjectID *uint
618 }{},
619 want: 99.,
620 },
621 {
622 name: "operator with typed nil 99",
623 expression: `ProjectID + 99`,
624 parameter: struct {
625 ProjectID *uint
626 }{},
627 want: "<nil>99",
628 },
629 {
630 name: "operator with typed nil if",
631 expression: `Flag ? 1 : 2`,
632 parameter: struct {
633 Flag *uint
634 }{},
635 want: 2.,
636 },
637 {
638 name: "Decimal math doesn't experience rounding error",
639 expression: "(x * 12.146) - y",
640 extension: decimalArithmetic,
641 parameter: map[string]interface{}{
642 "x": 12.5,
643 "y": -5,
644 },
645 want: decimal.NewFromFloat(156.825),
646 equalityFunc: decimalEqualityFunc,
647 },
648 {
649 name: "Decimal logical operators fractional difference",
650 expression: "((x * 12.146) - y) > 156.824999999",
651 extension: decimalArithmetic,
652 parameter: map[string]interface{}{
653 "x": 12.5,
654 "y": -5,
655 },
656 want: true,
657 },
658 {
659 name: "Decimal logical operators whole number difference",
660 expression: "((x * 12.146) - y) > 156",
661 extension: decimalArithmetic,
662 parameter: map[string]interface{}{
663 "x": 12.5,
664 "y": -5,
665 },
666 want: true,
667 },
668 {
669 name: "Decimal logical operators exact decimal match against GT",
670 expression: "((x * 12.146) - y) > 156.825",
671 extension: decimalArithmetic,
672 parameter: map[string]interface{}{
673 "x": 12.5,
674 "y": -5,
675 },
676 want: false,
677 },
678 {
679 name: "Decimal logical operators exact equality",
680 expression: "((x * 12.146) - y) == 156.825",
681 extension: decimalArithmetic,
682 parameter: map[string]interface{}{
683 "x": 12.5,
684 "y": -5,
685 },
686 want: true,
687 },
688 {
689 name: "Decimal mixes with string logic with force fail",
690 expression: `(((x * 12.146) - y) == 156.825) && a == "test" && !b && b`,
691 extension: decimalArithmetic,
692 parameter: map[string]interface{}{
693 "x": 12.5,
694 "y": -5,
695 "a": "test",
696 "b": false,
697 },
698 want: false,
699 },
700 },
701 t,
702 )
703 }
704
View as plain text