databricks-cli/libs/dyn/merge/override.go

212 lines
5.8 KiB
Go
Raw Permalink Normal View History

package merge
import (
"errors"
"fmt"
"github.com/databricks/cli/libs/dyn"
)
// OverrideVisitor is visiting the changes during the override process
// and allows to control what changes are allowed, or update the effective
// value.
//
// For instance, it can disallow changes outside the specific path(s), or update
// the location of the effective value.
//
// Values returned by 'VisitInsert' and 'VisitUpdate' are used as the final value
// of the node. 'VisitDelete' can return ErrOverrideUndoDelete to undo delete.
//
// 'VisitDelete' is called when a value is removed from mapping or sequence
// 'VisitInsert' is called when a new value is added to mapping or sequence
// 'VisitUpdate' is called when a leaf value is updated
type OverrideVisitor struct {
VisitDelete func(valuePath dyn.Path, left dyn.Value) error
VisitInsert func(valuePath dyn.Path, right dyn.Value) (dyn.Value, error)
VisitUpdate func(valuePath dyn.Path, left dyn.Value, right dyn.Value) (dyn.Value, error)
}
var ErrOverrideUndoDelete = errors.New("undo delete operation")
// Override overrides value 'leftRoot' with 'rightRoot', keeping 'location' if values
// haven't changed. Preserving 'location' is important to preserve the original source of the value
// for error reporting.
func Override(leftRoot dyn.Value, rightRoot dyn.Value, visitor OverrideVisitor) (dyn.Value, error) {
return override(dyn.EmptyPath, leftRoot, rightRoot, visitor)
}
func override(basePath dyn.Path, left dyn.Value, right dyn.Value, visitor OverrideVisitor) (dyn.Value, error) {
if left.Kind() != right.Kind() {
return visitor.VisitUpdate(basePath, left, right)
}
// NB: we only call 'VisitUpdate' on leaf values, and for sequences and mappings
// we don't know if value was updated or not
switch left.Kind() {
case dyn.KindMap:
merged, err := overrideMapping(basePath, left.MustMap(), right.MustMap(), visitor)
if err != nil {
return dyn.InvalidValue, err
}
return dyn.NewValue(merged, left.Locations()), nil
case dyn.KindSequence:
// some sequences are keyed, and we can detect which elements are added/removed/updated,
// but we don't have this information
merged, err := overrideSequence(basePath, left.MustSequence(), right.MustSequence(), visitor)
if err != nil {
return dyn.InvalidValue, err
}
return dyn.NewValue(merged, left.Locations()), nil
case dyn.KindString:
if left.MustString() == right.MustString() {
return left, nil
} else {
return visitor.VisitUpdate(basePath, left, right)
}
case dyn.KindFloat:
// TODO consider comparison with epsilon if normalization doesn't help, where do we use floats?
if left.MustFloat() == right.MustFloat() {
return left, nil
} else {
return visitor.VisitUpdate(basePath, left, right)
}
case dyn.KindBool:
if left.MustBool() == right.MustBool() {
return left, nil
} else {
return visitor.VisitUpdate(basePath, left, right)
}
case dyn.KindTime:
if left.MustTime() == right.MustTime() {
return left, nil
} else {
return visitor.VisitUpdate(basePath, left, right)
}
case dyn.KindInt:
if left.MustInt() == right.MustInt() {
return left, nil
} else {
return visitor.VisitUpdate(basePath, left, right)
}
case dyn.KindNil:
return left, nil
}
return dyn.InvalidValue, fmt.Errorf("unexpected kind %s at %s", left.Kind(), basePath.String())
}
func overrideMapping(basePath dyn.Path, leftMapping dyn.Mapping, rightMapping dyn.Mapping, visitor OverrideVisitor) (dyn.Mapping, error) {
out := dyn.NewMapping()
for _, leftPair := range leftMapping.Pairs() {
// detect if key was removed
if _, ok := rightMapping.GetPair(leftPair.Key); !ok {
path := basePath.Append(dyn.Key(leftPair.Key.MustString()))
err := visitor.VisitDelete(path, leftPair.Value)
// if 'delete' was undone, add it back
if errors.Is(err, ErrOverrideUndoDelete) {
err := out.Set(leftPair.Key, leftPair.Value)
if err != nil {
return dyn.NewMapping(), err
}
} else if err != nil {
return dyn.NewMapping(), err
}
}
}
// iterating only right mapping will remove keys not present anymore
// and insert new keys
for _, rightPair := range rightMapping.Pairs() {
if leftPair, ok := leftMapping.GetPair(rightPair.Key); ok {
path := basePath.Append(dyn.Key(rightPair.Key.MustString()))
newValue, err := override(path, leftPair.Value, rightPair.Value, visitor)
if err != nil {
return dyn.NewMapping(), err
}
// key was there before, so keep its location
err = out.Set(leftPair.Key, newValue)
if err != nil {
return dyn.NewMapping(), err
}
} else {
path := basePath.Append(dyn.Key(rightPair.Key.MustString()))
newValue, err := visitor.VisitInsert(path, rightPair.Value)
if err != nil {
return dyn.NewMapping(), err
}
err = out.Set(rightPair.Key, newValue)
if err != nil {
return dyn.NewMapping(), err
}
}
}
return out, nil
}
func overrideSequence(basePath dyn.Path, left []dyn.Value, right []dyn.Value, visitor OverrideVisitor) ([]dyn.Value, error) {
minLen := min(len(left), len(right))
var values []dyn.Value
for i := 0; i < minLen; i++ {
path := basePath.Append(dyn.Index(i))
merged, err := override(path, left[i], right[i], visitor)
if err != nil {
return nil, err
}
values = append(values, merged)
}
if len(right) > len(left) {
for i := minLen; i < len(right); i++ {
path := basePath.Append(dyn.Index(i))
newValue, err := visitor.VisitInsert(path, right[i])
if err != nil {
return nil, err
}
values = append(values, newValue)
}
} else if len(left) > len(right) {
for i := minLen; i < len(left); i++ {
path := basePath.Append(dyn.Index(i))
err := visitor.VisitDelete(path, left[i])
// if 'delete' was undone, add it back
if errors.Is(err, ErrOverrideUndoDelete) {
values = append(values, left[i])
} else if err != nil {
return nil, err
}
}
}
return values, nil
}