Rework/simplify expr.Vars handling

This commit is contained in:
Denis Arh
2021-04-01 21:10:51 +02:00
parent fc331cc388
commit fdf916b7f9
30 changed files with 436 additions and 324 deletions
+10 -10
View File
@@ -39,19 +39,19 @@ func TestVars(t *testing.T) {
var (
req = require.New(t)
vars = RVars{
vars = Must(Typify(map[string]interface{}{
"int": Must(NewInteger(42)),
"sub": RVars{
"sub": map[string]interface{}{
"foo": Must(NewString("foo")),
}.Vars(),
"three": RVars{
"two": RVars{
"one": RVars{
},
"three": map[string]interface{}{
"two": map[string]interface{}{
"one": map[string]interface{}{
"go": Must(NewString("!")),
}.Vars(),
}.Vars(),
}.Vars(),
}.Vars()
},
},
},
}))
tv = func(in interface{}) TypedValue {
switch cnv := in.(type) {
+8 -4
View File
@@ -550,8 +550,12 @@ func (t *UnsignedInteger) Assign(val interface{}) error {
}
}
// Vars is an expression type, wrapper for RVars type
type Vars struct{ value RVars }
// Vars is an expression type, wrapper for map[string]TypedValue type
type Vars struct{ value map[string]TypedValue }
func EmptyVars() *Vars {
return &Vars{make(map[string]TypedValue)}
}
// NewVars creates new instance of Vars expression type
func NewVars(val interface{}) (*Vars, error) {
@@ -566,12 +570,12 @@ func NewVars(val interface{}) (*Vars, error) {
func (t Vars) Get() interface{} { return t.value }
// Return underlying value on Vars
func (t Vars) GetValue() RVars { return t.value }
func (t Vars) GetValue() map[string]TypedValue { return t.value }
// Return type name
func (Vars) Type() string { return "Vars" }
// Convert value to RVars
// Convert value to map[string]TypedValue
func (Vars) Cast(val interface{}) (TypedValue, error) {
return NewVars(val)
}
+12 -3
View File
@@ -24,6 +24,10 @@ type (
}
)
func EmptyVars() *Vars {
return &Vars{value: map[string]TypedValue{}}
}
func ResolveTypes(rt resolvableType, resolver func(typ string) Type) error {
return rt.ResolveTypes(resolver)
}
@@ -36,9 +40,6 @@ func Typify(in interface{}) (tv TypedValue, err error) {
}
switch c := in.(type) {
// @todo
//case map[string]interface{}:
// return NewVars()
case []TypedValue:
return &Array{value: c}, nil
case bool:
@@ -75,6 +76,14 @@ func Typify(in interface{}) (tv TypedValue, err error) {
return &Duration{value: *c}, nil
case time.Duration:
return &Duration{value: c}, nil
case map[string]interface{}:
if v, err := CastToVars(c); err != nil {
return nil, err
} else {
return &Vars{v}, nil
}
case map[string]TypedValue:
return &Vars{c}, nil
case map[string]string:
return &KV{value: c}, nil
case map[string][]string:
+1 -1
View File
@@ -11,7 +11,7 @@ types:
as: '[]TypedValue'
Vars:
as: 'RVars'
as: 'map[string]TypedValue'
Boolean:
as: 'bool'
+15 -11
View File
@@ -9,13 +9,13 @@ import (
)
func TestTypedValueOperations(t *testing.T) {
scope := RVars{
"xUint": Must(NewUnsignedInteger(1)),
"xInt": Must(NewInteger(1)),
"xBoolT": Must(NewBoolean(true)),
"xBoolF": Must(NewBoolean(false)),
"xString": Must(NewString("foo")),
}.Vars()
scope, _ := NewVars(map[string]interface{}{
"xUint": uint(1),
"xInt": 1,
"xBoolT": true,
"xBoolF": false,
"xString": "foo",
})
tcc := []struct {
expects interface{}
@@ -27,11 +27,11 @@ func TestTypedValueOperations(t *testing.T) {
// uint ops
{true, "xUint == 1"},
{uint64(1), "xUint"},
{uint(1), "xUint"},
// uint ops
{true, "xInt == 1"},
{int64(1), "xInt"},
{int(1), "xInt"},
// string ops
{true, `xString == "foo"`},
@@ -157,12 +157,16 @@ func TestArrayDecode(t *testing.T) {
})
req.NoError(err)
req.NoError(RVars{
vars, err := NewVars(map[string]interface{}{
"strings": &Array{arr},
"iface": Must(NewString("typed")),
"typed": Must(NewString("typed")),
"values": &Array{arr},
}.Vars().Decode(&foo))
})
req.NoError(err)
req.NoError(vars.Decode(&foo))
req.Len(foo.Strings, 2)
req.Len(foo.Values, 2)
}
@@ -58,7 +58,7 @@ func isEmpty(i interface{}) bool {
}
switch reflect.TypeOf(i).Kind() {
case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Map:
case reflect.Slice, reflect.Array, reflect.Map:
return reflect.ValueOf(i).Len() == 0
}
+2 -2
View File
@@ -66,11 +66,11 @@ func (p *gvalParser) ParseEvaluators(ee ...Evaluator) error {
}
func (e *gvalEval) Eval(ctx context.Context, scope *Vars) (interface{}, error) {
return e.evaluable(ctx, scope.Dict())
return e.evaluable(ctx, scope)
}
func (e *gvalEval) Test(ctx context.Context, scope *Vars) (bool, error) {
r, err := e.evaluable(ctx, scope.Dict())
r, err := e.evaluable(ctx, scope)
if err != nil {
return false, err
}
+50 -33
View File
@@ -12,16 +12,6 @@ import (
"strings"
)
type (
// RVars or raw-vars, used as internal type for Vars expr type
RVars map[string]TypedValue
)
// Vars is a utility func that returns RVars wrapped in Vars
func (v RVars) Vars() *Vars {
return &Vars{value: v}
}
func (t Vars) Len() int {
return len(t.value)
}
@@ -36,7 +26,7 @@ func (t Vars) Select(k string) (TypedValue, error) {
func (t *Vars) AssignFieldValue(key string, val TypedValue) (err error) {
if t.value == nil {
t.value = make(RVars)
t.value = make(map[string]TypedValue)
}
t.value[key] = val
@@ -70,7 +60,7 @@ func (t Vars) ResolveTypes(res func(typ string) Type) (err error) {
// Assign takes base variables and assigns all new variables
func (t *Vars) Merge(nn ...Iterator) *Vars {
var (
out = &Vars{value: make(RVars)}
out = &Vars{value: make(map[string]TypedValue)}
)
nn = append([]Iterator{t}, nn...)
@@ -92,7 +82,7 @@ func (t *Vars) Copy(dst *Vars, kk ...string) {
}
if dst.value == nil {
dst.value = make(RVars)
dst.value = make(map[string]TypedValue)
}
for _, k := range kk {
@@ -135,17 +125,12 @@ func (t *Vars) HasAny(key string, kk ...string) bool {
return false
}
func (t *Vars) Dict() map[string]interface{} {
if t == nil {
return nil
}
var _ gval.Selector = &Vars{}
func (t *Vars) Dict() map[string]interface{} {
dict := make(map[string]interface{})
for k, v := range t.value {
switch v := v.(type) {
case gval.Selector:
dict[k] = v
case Dict:
dict[k] = v.Dict()
@@ -227,25 +212,31 @@ func (t *Vars) Value() (driver.Value, error) {
}
func (t Vars) SelectGVal(_ context.Context, k string) (interface{}, error) {
return t.Select(k)
val, err := t.Select(k)
switch c := val.(type) {
case gval.Selector:
return c, nil
default:
return UntypedValue(val), err
}
}
// UnmarshalJSON
func (t *Vars) UnmarshalJSON(in []byte) (err error) {
if len(in) == 0 {
return nil
}
var (
aux = make(map[string]*typedValueWrap)
)
if err = json.Unmarshal(in, &aux); err != nil {
return
if t.value == nil {
t.value = make(map[string]TypedValue)
}
if t.value == nil && len(aux) > 0 {
t.value = make(map[string]TypedValue)
if len(in) == 0 {
return nil
}
if err = json.Unmarshal(in, &aux); err != nil {
return
}
for k, v := range aux {
@@ -271,6 +262,15 @@ func (t *Vars) Each(fn func(k string, v TypedValue) error) (err error) {
return
}
func (t *Vars) Set(k string, v interface{}) (err error) {
if t.value == nil {
t.value = make(map[string]TypedValue)
}
t.value[k], err = Typify(v)
return
}
// UnmarshalJSON parses sort expression when passed inside JSON
func (t Vars) MarshalJSON() ([]byte, error) {
aux := make(map[string]*typedValueWrap)
@@ -307,8 +307,12 @@ func decode(dst reflect.Value, src TypedValue) (err error) {
return
}
raw := UntypedValue(src)
if reflect.ValueOf(src).Type().ConvertibleTo(dst.Type()) {
dst.Set(reflect.ValueOf(src))
return
}
raw := UntypedValue(src)
// Optimistically try to decode source to destination by comparing (internal) value type for destination
if reflect.ValueOf(raw).Type().ConvertibleTo(dst.Type()) {
dst.Set(reflect.ValueOf(raw))
@@ -349,6 +353,9 @@ func decode(dst reflect.Value, src TypedValue) (err error) {
dst.SetString(vString)
}
case reflect.Map:
dst.Set(reflect.ValueOf(src.Get()))
//case reflect.Interface:
// dst.Set(reflect.ValueOf(raw))
@@ -363,18 +370,28 @@ func decode(dst reflect.Value, src TypedValue) (err error) {
return nil
}
func CastToVars(val interface{}) (out RVars, err error) {
func CastToVars(val interface{}) (out map[string]TypedValue, err error) {
val = UntypedValue(val)
if val == nil {
return make(RVars), nil
return make(map[string]TypedValue), nil
}
switch c := val.(type) {
case *Vars:
return c.value, nil
case RVars:
case map[string]TypedValue:
return c, nil
case map[string]interface{}:
out = make(map[string]TypedValue)
for k, v := range c {
out[k], err = Typify(v)
if err != nil {
return
}
}
return
}
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
+39 -26
View File
@@ -9,7 +9,6 @@ import (
// extract typed-value
func TestVars_Decode(t *testing.T) {
t.Run("mix", func(t *testing.T) {
var (
req = require.New(t)
@@ -23,12 +22,12 @@ func TestVars_Decode(t *testing.T) {
Unexisting byte
}{}
vars = RVars{
vars, _ = NewVars(map[string]interface{}{
"int": Must(NewInteger(42)),
"STRING": Must(NewString("foo")),
"bool": Must(NewBoolean(true)),
"missing": Must(NewBoolean(true)),
}.Vars()
})
)
req.NoError(vars.Decode(dst))
@@ -48,11 +47,11 @@ func TestVars_Decode(t *testing.T) {
IBool interface{} `var:"iBool"`
}{}
vars = RVars{
vars, _ = NewVars(map[string]interface{}{
"iString": Must(NewString("foo")),
"iInteger": Must(NewInteger(42)),
"iBool": Must(NewBoolean(true)),
}.Vars()
})
)
req.NoError(vars.Decode(dst))
@@ -63,12 +62,12 @@ func TestVars_Decode(t *testing.T) {
req = require.New(t)
dst = &struct {
Vars RVars `var:"vars"`
Vars *Vars `var:"vars"`
}{}
vars = RVars{
"vars": RVars{"foo": Must(NewString("bar"))}.Vars(),
}.Vars()
vars, _ = NewVars(map[string]interface{}{
"vars": map[string]interface{}{"foo": Must(NewString("bar"))},
})
)
req.NoError(vars.Decode(dst))
@@ -83,10 +82,10 @@ func TestVars_Decode(t *testing.T) {
Uint64 uint64
}{}
vars = RVars{
vars, _ = NewVars(map[string]TypedValue{
"uint64": Must(NewAny("42")),
"int": Must(NewAny("42")),
}.Vars()
})
)
dst.Uint64 = 0
@@ -96,52 +95,66 @@ func TestVars_Decode(t *testing.T) {
req.Equal(uint64(42), dst.Uint64)
req.Equal(int64(42), dst.Int)
})
}
func TestVars_Assign(t *testing.T) {
var (
req = require.New(t)
vars = &Vars{}
)
req.NoError(Assign(vars, "foo", &String{value: "foo"}))
req.NoError(Assign(vars, "vars", &Vars{}))
req.NoError(Assign(vars, "vars.foo", &String{value: "foo"}))
}
func TestVars_UnmarshalJSON(t *testing.T) {
cases := []struct {
name string
json string
vars *Vars
vars map[string]interface{}
}{
{"empty", "", &Vars{}},
{"empty", "{}", &Vars{}},
{"string", `{"a":{"@value":"b"}}`, RVars{"a": &Unresolved{value: "b"}}.Vars()},
{"empty", "", make(map[string]interface{})},
{"object", "{}", make(map[string]interface{})},
{"string", `{"a":{"@value":"b"}}`, map[string]interface{}{"a": &Unresolved{value: "b"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var (
r = require.New(t)
v = &Vars{}
r = require.New(t)
unmarshaled = &Vars{}
aux, _ = NewVars(c.vars)
)
r.NoError(v.UnmarshalJSON([]byte(c.json)))
r.Equal(c.vars, v)
r.NoError(unmarshaled.UnmarshalJSON([]byte(c.json)))
r.Equal(aux, unmarshaled)
})
}
}
func TestVars_UMarshalJSON(t *testing.T) {
func TestVars_MarshalJSON(t *testing.T) {
cases := []struct {
name string
json string
vars *Vars
vars map[string]interface{}
}{
{"empty", "{}", &Vars{}},
{"string", `{"a":{"@value":"b","@type":"String"}}`, RVars{"a": &String{value: "b"}}.Vars()},
{"empty", "{}", nil},
{"string", `{"a":{"@value":"b","@type":"String"}}`, map[string]interface{}{"a": &String{value: "b"}}},
{"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()},
map[string]interface{}{"arr": &Array{value: []TypedValue{&String{value: "foo"}, &String{value: "bar"}}}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var (
r = require.New(t)
r = require.New(t)
aux, _ = NewVars(c.vars)
)
j, err := json.Marshal(c.vars)
j, err := json.Marshal(aux)
r.NoError(err)
r.Equal(c.json, string(j))
})