From 4a58d9c7cba7893d28559ec37a6c8eadc392addf Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sat, 27 Mar 2021 11:56:23 +0100 Subject: [PATCH] Fixed array & type handlign for gval&expr --- automation/automation/iterators.go | 2 +- automation/types/expr.go | 18 +- automation/types/expr_test.go | 39 +- pkg/expr/expr_types.go | 103 ++++- pkg/expr/func_arr.go | 124 ++++-- pkg/expr/func_arr_test.go | 583 ++++++++++++++++------------- pkg/expr/func_gen.go | 17 +- pkg/expr/func_gen_test.go | 12 +- pkg/expr/parser.go | 2 +- pkg/expr/vars_test.go | 29 +- 10 files changed, 598 insertions(+), 331 deletions(-) diff --git a/automation/automation/iterators.go b/automation/automation/iterators.go index f34987a7c..398d35435 100644 --- a/automation/automation/iterators.go +++ b/automation/automation/iterators.go @@ -71,7 +71,7 @@ func (i *collectionIterator) Next(context.Context, *Vars) (out *Vars, err error) case TypedValue: item = c default: - if item, err = NewAny(c); err != nil { + if item, err = Cast(c); err != nil { return } } diff --git a/automation/types/expr.go b/automation/types/expr.go index c3f5e9406..42ee68cfe 100644 --- a/automation/types/expr.go +++ b/automation/types/expr.go @@ -157,8 +157,22 @@ func (set ExprSet) Eval(ctx context.Context, in *expr.Vars) (*expr.Vars, error) typedValue, is := value.(expr.TypedValue) if !is { - if e.typ == nil { - typedValue, _ = expr.NewAny(value) + // value to be assigned (evaled, copied..) is not typed! + // try to figure out what we can do + if !knownType(e.typ) { + // Expression does not have type set + if out.Has(e.Target) { + t, _ := out.Select(e.Target) + typedValue, err = t.Cast(value) + if err != nil { + return nil, fmt.Errorf("cannot cast value %T to %s: %w", value, e.typ.Type(), err) + } + } else { + typedValue, err = expr.Cast(value) + if err != nil { + return nil, fmt.Errorf("cannot cast value %T to %s: %w", value, e.typ.Type(), err) + } + } } else if typedValue, err = e.typ.Cast(value); err != nil { return nil, fmt.Errorf("cannot cast value %T to %s (target %s)", value, e.typ.Type(), e.Target) } diff --git a/automation/types/expr_test.go b/automation/types/expr_test.go index f8b89501e..41f703aae 100644 --- a/automation/types/expr_test.go +++ b/automation/types/expr_test.go @@ -26,13 +26,13 @@ func TestExprSet_Eval(t *testing.T) { { name: "constant assignment", set: ExprSet{&Expr{Target: "foo", Expr: `"bar"`}}, - output: RVars{"foo": Must(NewAny("bar"))}, + output: RVars{"foo": Must(NewString("bar"))}, }, { name: "vars with path", set: ExprSet{&Expr{Target: "l1.l2", Expr: `"bar"`}}, input: RVars{"l1": RVars{}.Vars()}, - output: RVars{"l1": RVars{"l2": Must(NewAny("bar"))}.Vars()}, + output: RVars{"l1": RVars{"l2": Must(Cast("bar"))}.Vars()}, }, { name: "copy vars with same types", @@ -87,6 +87,41 @@ func TestExprSet_Eval(t *testing.T) { })), }, }, + { + name: "slice push", + set: ExprSet{ + &Expr{Target: "arr", typ: &Array{}, Expr: `[]`}, + &Expr{Target: "arr", typ: &Array{}, Expr: `push(arr, "foo")`}, + }, + output: RVars{ + "arr": Must(NewArray([]string{ + "foo", + })), + }, + }, + { + name: "slice push w/o type", + set: ExprSet{ + &Expr{Target: "arr", typ: &Array{}, Expr: `[]`}, + &Expr{Target: "arr", typ: &Any{}, Expr: `push(arr, "foo")`}, + }, + output: RVars{ + "arr": Must(NewArray([]string{ + "foo", + })), + }, + }, + { + name: "slice create & push w/o type", + set: ExprSet{ + &Expr{Target: "arr", typ: &Array{}, Expr: `push([], "foo")`}, + }, + output: RVars{ + "arr": Must(NewArray([]TypedValue{ + Must(Cast("foo")), + })), + }, + }, } ) diff --git a/pkg/expr/expr_types.go b/pkg/expr/expr_types.go index 4ced8e4f7..e1bf20ec2 100644 --- a/pkg/expr/expr_types.go +++ b/pkg/expr/expr_types.go @@ -5,17 +5,15 @@ import ( "context" "encoding/json" "fmt" - "github.com/davecgh/go-spew/spew" + "github.com/cortezaproject/corteza-server/pkg/errors" + "github.com/cortezaproject/corteza-server/pkg/handle" + "github.com/spf13/cast" "io" "net/http" "net/url" "reflect" "strings" "time" - - "github.com/cortezaproject/corteza-server/pkg/errors" - "github.com/cortezaproject/corteza-server/pkg/handle" - "github.com/spf13/cast" ) type ( @@ -29,6 +27,64 @@ func ResolveTypes(rt resolvableType, resolver func(typ string) Type) error { return rt.ResolveTypes(resolver) } +// cast intput into some well-known types +func Cast(in interface{}) (tv TypedValue, err error) { + var is bool + if tv, is = in.(TypedValue); is { + return + } + + switch c := in.(type) { + // @todo + //case map[string]interface{}: + // return NewVars() + case []TypedValue: + return &Array{value: c}, nil + case bool: + return &Boolean{value: c}, nil + case uint8: + return &UnsignedInteger{value: uint64(c)}, nil + case uint16: + return &UnsignedInteger{value: uint64(c)}, nil + case uint32: + return &UnsignedInteger{value: uint64(c)}, nil + case uint64: + return &UnsignedInteger{value: c}, nil + case int8: + return &Integer{value: int64(c)}, nil + case int16: + return &Integer{value: int64(c)}, nil + case int32: + return &Integer{value: int64(c)}, nil + case int64: + return &Integer{value: c}, nil + case float32: + return &Float{value: float64(c)}, nil + case float64: + return &Float{value: c}, nil + case string: + return &String{value: c}, nil + case []byte: + return &String{value: string(c)}, nil + case *time.Time: + return &DateTime{value: c}, nil + case time.Time: + return &DateTime{value: &c}, nil + case *time.Duration: + return &Duration{value: *c}, nil + case time.Duration: + return &Duration{value: c}, nil + case map[string]string: + return &KV{value: c}, nil + case map[string][]string: + return &KVV{value: c}, nil + case io.Reader, io.ReadCloser, io.ReadSeeker, io.ReadSeekCloser, io.ReadWriteSeeker: + return &Reader{value: c.(io.Reader)}, nil + default: + return &Any{value: c}, nil + } +} + // Unresolved is a special type that holds value + type it needs to be resolved to // // This solves problem with typed value serialization @@ -64,25 +120,27 @@ func CastToAny(val interface{}) (interface{}, error) { return val, nil } -func CastToArray(val interface{}) ([]TypedValue, error) { +func CastToArray(val interface{}) (out []TypedValue, err error) { switch val := val.(type) { + case nil: + return make([]TypedValue, 0), nil case *Array: return val.value, nil } ref := reflect.ValueOf(val) if ref.Kind() == reflect.Slice { - out := make([]TypedValue, ref.Len()) + out = make([]TypedValue, ref.Len()) for i := 0; i < ref.Len(); i++ { item := ref.Index(i).Interface() - if tVal, is := item.(TypedValue); is { - out[i] = tVal - } else { - out[i] = &Any{value: item} + out[i], err = Cast(item) + if err != nil { + return } } - return out, nil + + return } return nil, fmt.Errorf("unable to cast %T to []TypedValue", val) @@ -90,6 +148,24 @@ func CastToArray(val interface{}) ([]TypedValue, error) { var _ TypeValueDecoder = &Array{} +func (t Array) MarshalJSON() ([]byte, error) { + var ( + aux = make([]*typedValueWrap, len(t.value)) + ) + + for i, v := range t.value { + aux[i] = &typedValueWrap{Type: v.Type()} + + if _, is := v.(json.Marshaler); is { + aux[i].Value = v + } else { + aux[i].Value = v.Get() + } + } + + return json.Marshal(aux) +} + func (t *Array) Decode(dst reflect.Value) error { if dst.Kind() != reflect.Slice { return fmt.Errorf("failed to decode Array to non-slice") @@ -136,7 +212,7 @@ func (t Array) Has(k string) bool { } } -// Select is field accessor for *types.Record +// Select is field accessor for *types.Array // // Similar to SelectGVal but returns typed values func (t Array) Select(k string) (TypedValue, error) { @@ -261,7 +337,6 @@ func assignToKVV(t *KVV, key string, val TypedValue) error { t.value = make(map[string][]string) } - spew.Dump(val) str, err := cast.ToStringSliceE(val.Get()) t.value[key] = str return err diff --git a/pkg/expr/func_arr.go b/pkg/expr/func_arr.go index fd90fed8e..7d50d316d 100644 --- a/pkg/expr/func_arr.go +++ b/pkg/expr/func_arr.go @@ -1,9 +1,9 @@ package expr import ( - "reflect" - + "fmt" "github.com/PaesslerAG/gval" + "reflect" ) func ArrayFunctions() []gval.Language { @@ -18,24 +18,55 @@ func ArrayFunctions() []gval.Language { } // push adds a value to the end of slice, returns copy -func push(arr interface{}, p interface{}) (interface{}, error) { - if !isSlice(arr) { - return nil, &reflect.ValueError{Method: "Index", Kind: reflect.ValueOf(arr).Kind()} +func push(arr interface{}, nn ...interface{}) (out interface{}, err error) { + if arr == nil { + // If base is empty, return pushed items directly + return nn, nil + } else if i, is := arr.([]interface{}); is { + // Simple append if we're dealing with []interface{} base + return append(i, nn...), nil + } else if arr, err = toSlice(arr); err != nil { + return } - c := reflect.ValueOf(arr) + if stv, is := arr.([]TypedValue); is { + // slice of typed values, this will make things easier + for _, n := range nn { + if tv, is := n.(TypedValue); is { + stv = append(stv, tv) + } else { + // wrap unknown types... + stv = append(stv, Must(Cast(n))) + } + } + + return stv, nil + } + + var ( + c = reflect.ValueOf(arr) + nval = reflect.MakeSlice(c.Type(), c.Len()+len(nn), c.Cap()+len(nn)) + ) - nval := reflect.MakeSlice(c.Type(), c.Len()+1, c.Cap()+1) reflect.Copy(nval, c) - nval.Index(c.Len()).Set(reflect.ValueOf(p)) + + for i, n := range nn { + nt := reflect.ValueOf(n).Type() + it := nval.Index(c.Len() + i).Type() + if nt != it { + return nil, fmt.Errorf("can not push %v to %v slice", nt, it) + } + + nval.Index(c.Len() + i).Set(reflect.ValueOf(n)) + } return nval.Interface(), nil } // pop takes the last value in slice, does not modify original -func pop(arr interface{}) (interface{}, error) { - if !isSlice(arr) { - return nil, &reflect.ValueError{Method: "Index", Kind: reflect.ValueOf(arr).Kind()} +func pop(arr interface{}) (out interface{}, err error) { + if arr, err = toSlice(arr); err != nil { + return } c := reflect.ValueOf(arr) @@ -48,9 +79,9 @@ func pop(arr interface{}) (interface{}, error) { } // shifts takes the first value in slice, does not modify original -func shift(arr interface{}) (interface{}, error) { - if !isSlice(arr) { - return nil, &reflect.ValueError{Method: "Index", Kind: reflect.ValueOf(arr).Kind()} +func shift(arr interface{}) (out interface{}, err error) { + if arr, err = toSlice(arr); err != nil { + return } c := reflect.ValueOf(arr) @@ -62,51 +93,76 @@ func shift(arr interface{}) (interface{}, error) { return c.Index(0).Interface(), nil } -// cound gets the count of occurences in the slice -func count(arr interface{}, v ...interface{}) int { - if !isSlice(arr) { - return 0 +// count gets the count of occurrences in the slice +func count(arr interface{}, v ...interface{}) (count int, err error) { + if arr, err = toSlice(arr); err != nil { + return } - count := 0 - + var ( + occ int + ) for _, vv := range v { - if find(arr, vv) != -1 { + if occ, err = find(arr, vv); err != nil { + return 0, err + } else if occ != -1 { count++ } } - return count + return } -// has finds any occurence of the values in slice -func has(arr interface{}, v ...interface{}) bool { - return count(arr, v...) > 0 +// has finds any occurrence of the values in slice +func has(arr interface{}, v ...interface{}) (b bool, err error) { + if arr, err = toSlice(arr); err != nil { + return + } + + var c int + if c, err = count(arr, v...); err != nil { + return + } + + return c > 0, nil } -// hasAll finds all the occurences in the slice -func hasAll(arr interface{}, v ...interface{}) bool { return count(arr, v...) == len(v) } +// hasAll finds all the occurrences in the slice +func hasAll(arr interface{}, v ...interface{}) (b bool, err error) { + if arr, err = toSlice(arr); err != nil { + return + } + + var c int + if c, err = count(arr, v...); err != nil { + return + } + + return c == len(v), nil +} // find takes a value and gets the position in slice // if no results, returns -1 -func find(arr interface{}, v interface{}) int { - if !isSlice(arr) { - return 0 +func find(arr interface{}, v interface{}) (p int, err error) { + if arr, err = toSlice(arr); err != nil { + return } - for i := 0; i < reflect.ValueOf(arr).Len(); i++ { + for p = 0; p < reflect.ValueOf(arr).Len(); p++ { c := reflect.ValueOf(arr) - if c.Index(i).Interface() == v { - return i + if c.Index(p).Interface() == v { + return } } - return -1 + return -1, nil } // slice slices slices func slice(arr interface{}, start, end int) interface{} { + arr = UntypedValue(arr) + v := reflect.ValueOf(arr) if start >= v.Len() { diff --git a/pkg/expr/func_arr_test.go b/pkg/expr/func_arr_test.go index 1d4f28cec..bbfd14b4c 100644 --- a/pkg/expr/func_arr_test.go +++ b/pkg/expr/func_arr_test.go @@ -1,7 +1,7 @@ package expr import ( - "reflect" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -24,14 +24,6 @@ var ( } ) -type ( - fac struct { - expect interface{} - arr interface{} - val interface{} - } -) - func Example_push_int() { eval(`push(intArr, intVal)`, vals) @@ -81,298 +73,355 @@ func Example_pop_float() { // 69.42 } +func Test_push(t *testing.T) { + tcc := []struct { + base interface{} + new []interface{} + expect interface{} + expError bool + }{ + { + base: []string{"1", "2", "3"}, + new: []interface{}{"4"}, + expect: []string{"1", "2", "3", "4"}, + }, + { + base: Must(NewArray(nil)), + new: []interface{}{"foo"}, + expect: []TypedValue{Must(NewString("foo"))}, + }, + { + base: Must(NewArray([]interface{}{"foo"})), + new: []interface{}{"bar"}, + expect: []TypedValue{Must(NewString("foo")), Must(NewString("bar"))}, + }, + { + base: Must(NewArray([]interface{}{Must(NewString("foo"))})), + new: []interface{}{"bar"}, + expect: []TypedValue{Must(NewString("foo")), Must(NewString("bar"))}, + }, + { + base: Must(NewArray([]interface{}{Must(NewString("foo"))})), + new: []interface{}{Must(NewString("bar"))}, + expect: []TypedValue{Must(NewString("foo")), Must(NewString("bar"))}, + }, + { + base: []string{"1", "2", "3"}, + new: []interface{}{4}, + expError: true, + }, + } + + for p, tc := range tcc { + t.Run(fmt.Sprintf("%d", p), func(t *testing.T) { + var ( + req = require.New(t) + out, err = push(tc.base, tc.new...) + ) + + if tc.expError { + req.Error(err) + return + } + + req.NoError(err) + req.Equal(tc.expect, out) + }) + } +} + func Test_shift(t *testing.T) { - var ( - req = require.New(t) + tcc := []struct { + value interface{} + expect interface{} + expError bool + }{ + { + value: []string{"1", "2", "3"}, + expect: "1", + }, + { + value: map[string]string{"test": "123"}, + expect: nil, + expError: true, + }, + { + value: []int{4, 5, 6, 7}, + expect: 4, + }, + { + value: []int{}, + expect: nil, + }, + { + value: int(1), + expect: nil, + expError: true, + }, + { + value: []float64{11.1}, + expect: 11.1, + }, + } - tcc = []tc{ - { - value: []string{"1", "2", "3"}, - expect: "1", - }, - { - value: map[string]string{"test": "123"}, - expect: nil, - }, - { - value: []int{4, 5, 6, 7}, - expect: 4, - }, - { - value: []int{}, - expect: nil, - }, - { - value: int(1), - expect: nil, - }, - { - value: []float64{11.1}, - expect: 11.1, - }, - } - ) + for p, tc := range tcc { + t.Run(fmt.Sprintf("%d", p), func(t *testing.T) { + var ( + req = require.New(t) + val, err = shift(tc.value) + ) - for _, tst := range tcc { - val, _ := shift(tst.value) + if tc.expError { + req.Error(err) + return + } - req.Equal(tst.expect, val) + req.NoError(err) + req.Equal(tc.expect, val) + }) } } func Test_find(t *testing.T) { - var ( - req = require.New(t) + tcc := []struct { + expect interface{} + arr interface{} + val interface{} + }{ + { + arr: []string{"1", "2", "3"}, + val: "3", + expect: 2, + }, + { + arr: []bool{true, false, true}, + val: true, + expect: 0, + }, + { + arr: []int{4, 5, 6, 7}, + val: 7, + expect: 3, + }, + { + arr: []int{}, + val: 0, + expect: -1, + }, + { + arr: []float64{11.1, 12.4}, + val: 11.1, + expect: 0, + }, + { + arr: []float64{11.1, 12.4}, + val: 11.2, + expect: -1, + }, + } - tcc = []fac{ - { - arr: []string{"1", "2", "3"}, - val: "3", - expect: 2, - }, - { - arr: []bool{true, false, true}, - val: true, - expect: 0, - }, - { - arr: []int{4, 5, 6, 7}, - val: 7, - expect: 3, - }, - { - arr: []int{}, - val: 0, - expect: -1, - }, - { - arr: []float64{11.1, 12.4}, - val: 11.1, - expect: 0, - }, - { - arr: []float64{11.1, 12.4}, - val: 11.2, - expect: -1, - }, - } - ) + for p, tc := range tcc { + t.Run(fmt.Sprintf("%d", p), func(t *testing.T) { + var ( + req = require.New(t) + loc, err = find(tc.arr, tc.val) + ) - for _, tst := range tcc { - loc := find(tst.arr, tst.val) - - req.Equal(tst.expect, loc) + req.NoError(err) + req.Equal(tc.expect, loc) + }) } } func Test_count(t *testing.T) { - var ( - req = require.New(t) + tcc := []struct { + expect interface{} + arr interface{} + val []interface{} + }{ + { + arr: []string{"1", "2", "3"}, + val: []interface{}{"0", "3"}, + expect: 1, + }, + { + arr: []bool{true, true}, + val: []interface{}{false, false}, + expect: 0, + }, + { + arr: []bool{true, true}, + val: []interface{}{false, true}, + expect: 1, + }, + { + arr: []int{4, 5, 6, 7}, + val: []interface{}{7, 4}, + expect: 2, + }, + { + arr: []float64{11.1, 12.4}, + val: []interface{}{0.1, 1.1}, + expect: 0, + }, + } - tcc = []fac{ - { - arr: []string{"1", "2", "3"}, - val: []string{"0", "3"}, - expect: 1, - }, - { - arr: []bool{true, true}, - val: []bool{false, false}, - expect: 0, - }, - { - arr: []bool{true, true}, - val: []bool{false, true}, - expect: 1, - }, - { - arr: []int{4, 5, 6, 7}, - val: []int{7, 4}, - expect: 2, - }, - { - arr: []float64{11.1, 12.4}, - val: []float64{0.1, 1.1}, - expect: 0, - }, - } - ) + for p, tc := range tcc { + t.Run(fmt.Sprintf("%d", p), func(t *testing.T) { + var ( + req = require.New(t) + err error + loc int + ) - for _, tst := range tcc { - var loc int - - switch reflect.TypeOf(tst.val).Elem().Kind() { - case reflect.String: - loc = count(tst.arr, tst.val.([]string)[0], tst.val.([]string)[1]) - break - case reflect.Bool: - loc = count(tst.arr, tst.val.([]bool)[0], tst.val.([]bool)[1]) - break - case reflect.Int: - loc = count(tst.arr, tst.val.([]int)[0], tst.val.([]int)[1]) - break - case reflect.Float64: - loc = count(tst.arr, tst.val.([]float64)[0], tst.val.([]float64)[1]) - break - } - - req.Equal(tst.expect, loc) + loc, err = count(tc.arr, tc.val...) + req.NoError(err) + req.Equal(tc.expect, loc) + }) } } func Test_has(t *testing.T) { - var ( - req = require.New(t) + tcc := []struct { + expect interface{} + arr interface{} + val []interface{} + }{ + { + arr: []string{"1", "2", "3"}, + val: []interface{}{"0", "3"}, + expect: true, + }, + { + arr: []bool{true, true}, + val: []interface{}{false, false}, + expect: false, + }, + { + arr: []bool{true, true}, + val: []interface{}{false, true}, + expect: true, + }, + { + arr: []int{4, 5, 6, 7}, + val: []interface{}{7, 4}, + expect: true, + }, + { + arr: []float64{11.1, 12.4}, + val: []interface{}{0.1, 1.1}, + expect: false, + }, + } - tcc = []fac{ - { - arr: []string{"1", "2", "3"}, - val: []string{"0", "3"}, - expect: true, - }, - { - arr: []bool{true, true}, - val: []bool{false, false}, - expect: false, - }, - { - arr: []bool{true, true}, - val: []bool{false, true}, - expect: true, - }, - { - arr: []int{4, 5, 6, 7}, - val: []int{7, 4}, - expect: true, - }, - { - arr: []float64{11.1, 12.4}, - val: []float64{0.1, 1.1}, - expect: false, - }, - } - ) + for p, tc := range tcc { + t.Run(fmt.Sprintf("%d", p), func(t *testing.T) { + var ( + req = require.New(t) + loc bool + err error + ) - for _, tst := range tcc { - var loc bool - - switch reflect.TypeOf(tst.val).Elem().Kind() { - case reflect.String: - loc = has(tst.arr, tst.val.([]string)[0], tst.val.([]string)[1]) - break - case reflect.Bool: - loc = has(tst.arr, tst.val.([]bool)[0], tst.val.([]bool)[1]) - break - case reflect.Int: - loc = has(tst.arr, tst.val.([]int)[0], tst.val.([]int)[1]) - break - case reflect.Float64: - loc = has(tst.arr, tst.val.([]float64)[0], tst.val.([]float64)[1]) - break - } - - req.Equal(tst.expect, loc) + loc, err = has(tc.arr, tc.val...) + req.NoError(err) + req.Equal(tc.expect, loc) + }) } } func Test_hasAll(t *testing.T) { - var ( - req = require.New(t) + tcc := []struct { + arr interface{} + val []interface{} + hasAll bool + expError bool + }{ + { + arr: []string{"1", "2", "3"}, + val: []interface{}{"0", "3"}, + hasAll: false, + }, + { + arr: []bool{true, true}, + val: []interface{}{false, false}, + hasAll: false, + }, + { + arr: []bool{true, true}, + val: []interface{}{false, true}, + hasAll: false, + }, + { + arr: []int{4, 5, 6, 7}, + val: []interface{}{7, 4}, + hasAll: true, + }, + { + arr: []float64{11.1, 12.4}, + val: []interface{}{0.1, 1.1}, + hasAll: false, + }, + } - tcc = []fac{ - { - arr: []string{"1", "2", "3"}, - val: []string{"0", "3"}, - expect: false, - }, - { - arr: []bool{true, true}, - val: []bool{false, false}, - expect: false, - }, - { - arr: []bool{true, true}, - val: []bool{false, true}, - expect: false, - }, - { - arr: []int{4, 5, 6, 7}, - val: []int{7, 4}, - expect: true, - }, - { - arr: []float64{11.1, 12.4}, - val: []float64{0.1, 1.1}, - expect: false, - }, - } - ) + for p, tc := range tcc { + t.Run(fmt.Sprintf("%d", p), func(t *testing.T) { + var ( + req = require.New(t) + rval, err = hasAll(tc.arr, tc.val...) + ) - for _, tst := range tcc { - var loc bool + if tc.expError { + req.Error(err) + return + } - switch reflect.TypeOf(tst.arr).Elem().Kind() { - case reflect.String: - loc = hasAll(tst.arr, tst.val.([]string)[0], tst.val.([]string)[1]) - break - case reflect.Bool: - loc = hasAll(tst.arr, tst.val.([]bool)[0], tst.val.([]bool)[1]) - break - case reflect.Int: - loc = hasAll(tst.arr, tst.val.([]int)[0], tst.val.([]int)[1]) - break - case reflect.Float64: - loc = hasAll(tst.arr, tst.val.([]float64)[0], tst.val.([]float64)[1]) - break - } - - req.Equal(tst.expect, loc) + req.NoError(err) + req.Equal(tc.hasAll, rval) + }) } } func Test_slice(t *testing.T) { - type ( - sct struct { - vals []int - arr interface{} - expect interface{} - } - ) + tcc := []struct { + vals []int + arr interface{} + expect interface{} + }{ + { + vals: []int{0, 3}, + arr: []string{"1", "2", "3"}, + expect: []string{"1", "2", "3"}, + }, + { + vals: []int{0, 1}, + arr: []string{"1", "2", "3"}, + expect: []string{"1"}, + }, + { + vals: []int{2, 3}, + arr: []bool{true, true}, + expect: []bool{true, true}, + }, + { + vals: []int{1, -1}, + arr: []int{4, 5, 6, 7}, + expect: []int{5, 6, 7}, + }, + { + vals: []int{3, -1}, + arr: []float64{11.1, 12.4}, + expect: []float64{11.1, 12.4}, + }, + } - var ( - req = require.New(t) + for p, tc := range tcc { + t.Run(fmt.Sprintf("%d", p), func(t *testing.T) { + var ( + req = require.New(t) + ss = slice(tc.arr, tc.vals[0], tc.vals[1]) + ) - tcc = []sct{ - { - vals: []int{0, 3}, - arr: []string{"1", "2", "3"}, - expect: []string{"1", "2", "3"}, - }, - { - vals: []int{0, 1}, - arr: []string{"1", "2", "3"}, - expect: []string{"1"}, - }, - { - vals: []int{2, 3}, - arr: []bool{true, true}, - expect: []bool{true, true}, - }, - { - vals: []int{1, -1}, - arr: []int{4, 5, 6, 7}, - expect: []int{5, 6, 7}, - }, - { - vals: []int{3, -1}, - arr: []float64{11.1, 12.4}, - expect: []float64{11.1, 12.4}, - }, - } - ) - - for _, tst := range tcc { - new := slice(tst.arr, tst.vals[0], tst.vals[1]) - req.Equal(tst.expect, new) + req.Equal(tc.expect, ss) + }) } } diff --git a/pkg/expr/func_gen.go b/pkg/expr/func_gen.go index 0bf3e1da5..b4f9dcbd8 100644 --- a/pkg/expr/func_gen.go +++ b/pkg/expr/func_gen.go @@ -1,6 +1,7 @@ package expr import ( + "fmt" "reflect" "github.com/PaesslerAG/gval" @@ -88,5 +89,19 @@ func getKind(v interface{}) reflect.Kind { } func isSlice(v interface{}) bool { - return reflect.TypeOf(v).Kind() == reflect.Slice + return reflect.TypeOf(v).Kind() == reflect.Slice || reflect.TypeOf(v).Kind() == reflect.Array +} + +// toArray removes expr types (if wrapped) and checks if the variable is slice +// internal only +// +// +func toSlice(vv interface{}) (interface{}, error) { + vv = UntypedValue(vv) + + if !isSlice(vv) { + return nil, fmt.Errorf("unexpected type: %T, expecting slice", vv) + } + + return vv, nil } diff --git a/pkg/expr/func_gen_test.go b/pkg/expr/func_gen_test.go index c8dcbdce8..4a84145cc 100644 --- a/pkg/expr/func_gen_test.go +++ b/pkg/expr/func_gen_test.go @@ -6,13 +6,6 @@ import ( "github.com/stretchr/testify/require" ) -type ( - tc struct { - value interface{} - expect interface{} - } -) - func Test_empty(t *testing.T) { var ( req = require.New(t) @@ -23,7 +16,10 @@ func Test_empty(t *testing.T) { unsetString string unsetInt64 int64 - tcc = []tc{ + tcc = []struct { + value interface{} + expect interface{} + }{ { value: []string{}, expect: true, diff --git a/pkg/expr/parser.go b/pkg/expr/parser.go index ee966d308..0cf599436 100644 --- a/pkg/expr/parser.go +++ b/pkg/expr/parser.go @@ -96,6 +96,6 @@ func eval(e string, p interface{}) { if err != nil { fmt.Printf("error: %v", err) } else { - fmt.Printf("%v", result) + fmt.Printf("%+v", result) } } diff --git a/pkg/expr/vars_test.go b/pkg/expr/vars_test.go index 499ed743f..0bb52a269 100644 --- a/pkg/expr/vars_test.go +++ b/pkg/expr/vars_test.go @@ -1,6 +1,7 @@ package expr import ( + "encoding/json" "github.com/stretchr/testify/require" "testing" ) @@ -89,7 +90,7 @@ func TestVars_UnmarshalJSON(t *testing.T) { }{ {"empty", "", &Vars{}}, {"empty", "{}", &Vars{}}, - {"empty", `{"a":{"@value":"b"}}`, RVars{"a": &Unresolved{value: "b"}}.Vars()}, + {"string", `{"a":{"@value":"b"}}`, RVars{"a": &Unresolved{value: "b"}}.Vars()}, } for _, c := range cases { @@ -104,3 +105,29 @@ func TestVars_UnmarshalJSON(t *testing.T) { }) } } + +func TestVars_UMarshalJSON(t *testing.T) { + cases := []struct { + name string + json string + vars *Vars + }{ + {"empty", "{}", &Vars{}}, + {"string", `{"a":{"@value":"b","@type":"String"}}`, RVars{"a": &String{value: "b"}}.Vars()}, + {"array", + `{"arr":{"@value":[{"@value":"foo","@type":"String"},{"@value":"bar","@type":"String"}],"@type":"Array"}}`, + RVars{"arr": &Array{value: []TypedValue{&String{value: "foo"}, &String{value: "bar"}}}}.Vars()}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var ( + r = require.New(t) + ) + + j, err := json.Marshal(c.vars) + r.NoError(err) + r.Equal(c.json, string(j)) + }) + } +}