Rework/simplify expr.Vars handling
This commit is contained in:
+10
-10
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -11,7 +11,7 @@ types:
|
||||
as: '[]TypedValue'
|
||||
|
||||
Vars:
|
||||
as: 'RVars'
|
||||
as: 'map[string]TypedValue'
|
||||
|
||||
Boolean:
|
||||
as: 'bool'
|
||||
|
||||
+15
-11
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user