Skip to content

Commit

Permalink
fix patching map[string]...
Browse files Browse the repository at this point in the history
  • Loading branch information
purehyperbole committed Jun 28, 2021
1 parent e65b2c5 commit a077fc0
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 8 deletions.
3 changes: 2 additions & 1 deletion diff_examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ func ExamplePatchWithErrors() {
patchLog := Patch(changelog, c)

//this also demonstrated the nested errors with 'next'
errors := patchLog[7].Errors.(*DiffError)

errors := patchLog[0].Errors.(*DiffError)

//we can also continue to nest errors if we like
message := errors.WithCause(NewError("This is a custom message")).
Expand Down
4 changes: 3 additions & 1 deletion patch.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package diff

import "reflect"
import (
"reflect"
)

/**
This is a method of applying a changelog to a value or struct. change logs
Expand Down
51 changes: 45 additions & 6 deletions patch_map.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package diff

import (
"errors"
"reflect"

"github.com/vmihailenco/msgpack"
Expand All @@ -12,15 +13,36 @@ func (d *Differ) renderMap(c *ChangeValue) (m, k, v *reflect.Value) {
//we must tease out the type of the key, we use the msgpack from diff to recreate the key
kt := c.target.Type().Key()
field := reflect.New(kt)
if err := msgpack.Unmarshal([]byte(c.change.Path[c.pos]), field.Interface()); err != nil {
c.SetFlag(FlagIgnored)
c.AddError(NewError("Unable to unmarshal path element to target type for key in map", err))
return

if d.StructMapKeys {
if err := msgpack.Unmarshal([]byte(c.change.Path[c.pos]), field.Interface()); err != nil {
c.SetFlag(FlagIgnored)
c.AddError(NewError("Unable to unmarshal path element to target type for key in map", err))
return
}
c.key = field.Elem()
} else {
c.key = reflect.ValueOf(c.change.Path[c.pos])
}
c.key = field.Elem()

if c.target.IsNil() && c.target.IsValid() {
c.target.Set(reflect.MakeMap(c.target.Type()))
}

// we need to check that MapIndex does not panic here
// when the key type is not a string
defer func() {
if err := recover(); err != nil {
switch x := err.(type) {
case error:
c.AddError(NewError("Unable to unmarshal path element to target type for key in map", x))
case string:
c.AddError(NewError("Unable to unmarshal path element to target type for key in map", errors.New(x)))
}
c.SetFlag(FlagIgnored)
}
}()

x := c.target.MapIndex(c.key)

if !x.IsValid() && c.change.Type != DELETE && !c.HasFlag(OptionNoCreate) {
Expand All @@ -41,6 +63,7 @@ func (d *Differ) renderMap(c *ChangeValue) (m, k, v *reflect.Value) {
mp := *c.target //these may change out from underneath us as we recurse
key := c.key //so we make copies and pass back pointers to them
c.swap(&x)

return &mp, &key, &x

}
Expand All @@ -49,7 +72,11 @@ func (d *Differ) renderMap(c *ChangeValue) (m, k, v *reflect.Value) {
// container type etc. We have to have special handling for each
// type. Set values are more generic even if they must be instanced
func (d *Differ) deleteMapEntry(c *ChangeValue, m, k, v *reflect.Value) {
if m != nil && m.CanSet() && v.IsValid() {
if k == nil {
return
}

if m != nil && m.CanSet() && v.IsValid() && v.Kind() != reflect.Int {
for x := 0; x < v.NumField(); x++ {
if !v.Field(x).IsZero() {
m.SetMapIndex(*k, *v)
Expand All @@ -58,5 +85,17 @@ func (d *Differ) deleteMapEntry(c *ChangeValue, m, k, v *reflect.Value) {
} //if all the fields are zero, remove from map
m.SetMapIndex(*k, reflect.Value{})
c.SetFlag(FlagDeleted)
} else {
switch c.change.Type {
case DELETE:
m.SetMapIndex(*k, reflect.Value{})
c.SetFlag(FlagDeleted)
case CREATE:
m.SetMapIndex(*k, *v)
c.SetFlag(FlagCreated)
case UPDATE:
m.SetMapIndex(*k, *v)
c.SetFlag(FlagUpdated)
}
}
}
12 changes: 12 additions & 0 deletions patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,18 @@ func TestPatch(t *testing.T) {
},
nil,
},

{
"map",
map[string]interface{}{"1": "one", "3": "three"},
map[string]interface{}{"2": "two", "3": "tres"},
Changelog{
Change{Type: DELETE, Path: []string{"1"}, From: "one", To: nil},
Change{Type: CREATE, Path: []string{"2"}, From: nil, To: "two"},
Change{Type: UPDATE, Path: []string{"3"}, From: "three", To: "tres"},
},
nil,
},
}

for _, tc := range cases {
Expand Down

0 comments on commit a077fc0

Please sign in to comment.