diff --git a/pkg/expr/expr.go b/pkg/expr/expr.go index a1ceaac9c..4dea7617f 100644 --- a/pkg/expr/expr.go +++ b/pkg/expr/expr.go @@ -20,7 +20,8 @@ func PathSplit(path string) ([]string, error) { s.Split(pathSplitter) for s.Scan() { - if len(s.Text()) == 0 { + // checks if two consecutive path parts are empty + if len(s.Text()) == 0 && len(out) > 0 && len(out[len(out)-1]) == 0 { return nil, invalidPathErr } out = append(out, s.Text()) diff --git a/pkg/expr/expr_test.go b/pkg/expr/expr_test.go index c0a342aef..29ceecda4 100644 --- a/pkg/expr/expr_test.go +++ b/pkg/expr/expr_test.go @@ -1,8 +1,9 @@ package expr import ( - "github.com/stretchr/testify/require" "testing" + + "github.com/stretchr/testify/require" ) func TestPathSplit(t *testing.T) { @@ -16,6 +17,7 @@ func TestPathSplit(t *testing.T) { {p: "a.b[1]", r: []string{"a", "b", "1"}}, {p: "a.b[1].baz[0]", r: []string{"a", "b", "1", "baz", "0"}}, {p: "a.[]", err: invalidPathErr.Error()}, + {p: "a[]", r: []string{"a", ""}}, {p: "a[1]bzz", err: invalidPathErr.Error()}, } diff --git a/pkg/expr/expr_types.go b/pkg/expr/expr_types.go index e896bd7e2..b451ab4c5 100644 --- a/pkg/expr/expr_types.go +++ b/pkg/expr/expr_types.go @@ -540,18 +540,56 @@ func (t *KV) Delete(keys ...string) (out TypedValue, err error) { return kv, nil } -func (t *KVV) AssignFieldValue(key string, val TypedValue) error { +func (t *KVV) AssignFieldValue(key []string, val TypedValue) error { return assignToKVV(t, key, val) } -func assignToKVV(t *KVV, key string, val TypedValue) error { +func assignToKVV(t *KVV, kk []string, val TypedValue) error { if t.value == nil { t.value = make(map[string][]string) } - str, err := cast.ToStringSliceE(val.Get()) - t.value[key] = str - return err + switch len(kk) { + case 2: + str, err := cast.ToStringE(val.Get()) + if err != nil { + return err + } + + key, ind := kk[0], kk[1] + + if len(ind) > 0 { + // handles kvv.field[42] = "value" + index, err := cast.ToIntE(ind) + if err != nil { + return err + } + + if index >= 0 && index < len(t.value[key]) { + // handles positive & in-range indexes + t.value[key][index] = str + return nil + } + + //negative & out-of-range indexes are always appended + } + + // handles kvv.field[] = "value" + t.value[key] = append(t.value[key], str) + + case 1: + str, err := cast.ToStringSliceE(val.Get()) + if err != nil { + return err + } + + t.value[kk[0]] = str + + default: + return fmt.Errorf("cannot set value on %s with path '%s'", t.Type(), strings.Join(kk, ".")) + } + + return nil } func CastToKVV(val interface{}) (out map[string][]string, err error) { diff --git a/pkg/expr/expr_types_test.go b/pkg/expr/expr_types_test.go index 539da9219..e386fbb19 100644 --- a/pkg/expr/expr_types_test.go +++ b/pkg/expr/expr_types_test.go @@ -362,6 +362,17 @@ func TestKVV_Assign(t *testing.T) { req.NoError(kvv.Assign(url.Values{"foo": []string{"bar"}})) req.Contains(kvv.value, "foo") req.Equal([]string{"bar"}, kvv.value["foo"]) + + kvv = KVV{} + req.NoError(Assign(&kvv, "deep", Must(NewString("bar")))) + req.NoError(Assign(&kvv, "deep[0]", Must(NewString("bar")))) + req.NoError(Assign(&kvv, "deep[]", Must(NewString("baz")))) + req.NoError(Assign(&kvv, "deep[]", Must(NewString("bar")))) + req.NoError(Assign(&kvv, "deep[3]", Must(NewString("baz")))) + req.NoError(Assign(&kvv, "deep[3]", Must(NewString("b4z")))) + req.Contains(kvv.value, "deep") + req.Equal([]string{"bar", "baz", "bar", "b4z"}, kvv.value["deep"]) + } func TestKVV_Set(t *testing.T) {