294 lines
8.3 KiB
Go
294 lines
8.3 KiB
Go
|
package tftypes
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"math/big"
|
||
|
)
|
||
|
|
||
|
// ValueDiff expresses a subset of a Value that is different between two
|
||
|
// Values. The Path property indicates where the subset is located within the
|
||
|
// Value, and Value1 and Value2 indicate what the subset is in each of the
|
||
|
// Values. If the Value does not contain a subset at that AttributePath, its
|
||
|
// Value will be nil. This is distinct from a Value with a nil in it (a "null"
|
||
|
// value), which is present in the Value.
|
||
|
type ValueDiff struct {
|
||
|
// The Path these different subsets are located at in the original
|
||
|
// Values.
|
||
|
Path *AttributePath
|
||
|
|
||
|
// The subset of the first Value passed to Diff found at the
|
||
|
// AttributePath indicated by Path.
|
||
|
Value1 *Value
|
||
|
|
||
|
// The subset of the second Value passed to Diff found at the
|
||
|
// AttributePath indicated by Path.
|
||
|
Value2 *Value
|
||
|
}
|
||
|
|
||
|
func (v ValueDiff) String() string {
|
||
|
val1 := "{no value set}"
|
||
|
if v.Value1 != nil {
|
||
|
val1 = v.Value1.String()
|
||
|
}
|
||
|
val2 := "{no value set}"
|
||
|
if v.Value2 != nil {
|
||
|
val2 = v.Value2.String()
|
||
|
}
|
||
|
return fmt.Sprintf("%s: value1: %s, value2: %s",
|
||
|
v.Path.String(), val1, val2)
|
||
|
}
|
||
|
|
||
|
// Equal returns whether two ValueDiffs should be considered equal or not.
|
||
|
// ValueDiffs are consisdered equal when their Path, Value1, and Value2
|
||
|
// properties are considered equal.
|
||
|
func (v ValueDiff) Equal(o ValueDiff) bool {
|
||
|
if !v.Path.Equal(o.Path) {
|
||
|
return false
|
||
|
}
|
||
|
if v.Value1 == nil && o.Value1 != nil {
|
||
|
return false
|
||
|
}
|
||
|
if v.Value1 != nil && o.Value1 == nil {
|
||
|
return false
|
||
|
}
|
||
|
if v.Value1 != nil && o.Value1 != nil && !v.Value1.Equal(*o.Value1) {
|
||
|
return false
|
||
|
}
|
||
|
if v.Value2 == nil && o.Value2 != nil {
|
||
|
return false
|
||
|
}
|
||
|
if v.Value2 != nil && o.Value2 == nil {
|
||
|
return false
|
||
|
}
|
||
|
if v.Value2 != nil && o.Value2 != nil && !v.Value2.Equal(*o.Value2) {
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// Diff computes the differences between `val1` and `val2` and surfaces them as
|
||
|
// a slice of ValueDiffs. The ValueDiffs in the struct will use `val1`'s values
|
||
|
// as Value1 and `val2`'s values as Value2. An empty or nil slice means the two
|
||
|
// Values can be considered equal. Values must be the same type when passed to
|
||
|
// Diff; passing in Values of two different types will result in an error. If
|
||
|
// both Values are empty, they are considered equal. If one Value is missing
|
||
|
// type, it will result in an error. val1.Type().Is(val2.Type()) is a safe way
|
||
|
// to check that Values can be compared with Diff.
|
||
|
func (val1 Value) Diff(val2 Value) ([]ValueDiff, error) {
|
||
|
var diffs []ValueDiff
|
||
|
|
||
|
if val1.Type() == nil && val2.Type() == nil && val1.value == nil && val2.value == nil {
|
||
|
return diffs, nil
|
||
|
}
|
||
|
if (val1.Type() == nil && val2.Type() != nil) || (val1.Type() != nil && val2.Type() == nil) {
|
||
|
return nil, errors.New("cannot diff value missing type")
|
||
|
}
|
||
|
if !val1.Type().Is(val2.Type()) {
|
||
|
return nil, errors.New("Can't diff values of different types")
|
||
|
}
|
||
|
|
||
|
// make sure everything in val2 is also in val1
|
||
|
err := Walk(val2, func(path *AttributePath, value2 Value) (bool, error) {
|
||
|
_, _, err := WalkAttributePath(val1, path)
|
||
|
if err != nil && err != ErrInvalidStep {
|
||
|
return false, fmt.Errorf("Error walking %q: %w", path, err)
|
||
|
} else if err == ErrInvalidStep {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: nil,
|
||
|
Value2: &value2,
|
||
|
})
|
||
|
return false, nil
|
||
|
}
|
||
|
return true, nil
|
||
|
})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// make sure everything in val1 is also in val2 and also that it all matches
|
||
|
err = Walk(val1, func(path *AttributePath, value1 Value) (bool, error) {
|
||
|
// pull out the Value at the same path in val2
|
||
|
value2I, _, err := WalkAttributePath(val2, path)
|
||
|
if err != nil && err != ErrInvalidStep {
|
||
|
return false, fmt.Errorf("Error walking %q: %w", path, err)
|
||
|
} else if err == ErrInvalidStep {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: &value1,
|
||
|
Value2: nil,
|
||
|
})
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
// convert from an interface{} to a Value
|
||
|
value2 := value2I.(Value)
|
||
|
|
||
|
// if they're both unknown, no need to continue
|
||
|
if !value1.IsKnown() && !value2.IsKnown() {
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
// if val1 is unknown and val2 not, we have a diff
|
||
|
// no need to continue to recurse into val1, no further to go
|
||
|
if !value1.IsKnown() && value2.IsKnown() {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: &value1,
|
||
|
Value2: &value2,
|
||
|
})
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
// if val2 is unknown and val1 not, we have a diff
|
||
|
// continue to recurse though, so we can surface the elements of val1
|
||
|
// that are now "missing" as diffs
|
||
|
if value1.IsKnown() && !value2.IsKnown() {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: &value1,
|
||
|
Value2: &value2,
|
||
|
})
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
// if they're both null, no need to continue
|
||
|
if value1.IsNull() && value2.IsNull() {
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
// if val1 is null and val2 not, we have a diff
|
||
|
// no need to continue to recurse into val1, no further to go
|
||
|
if value1.IsNull() && !value2.IsNull() {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: &value1,
|
||
|
Value2: &value2,
|
||
|
})
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
// if val2 is null and val1 not, we have a diff
|
||
|
// continue to recurse though, so we can surface the elements of val1
|
||
|
// that are now "missing" as diffs
|
||
|
if !value1.IsNull() && value2.IsNull() {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: &value1,
|
||
|
Value2: &value2,
|
||
|
})
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
// we know there are known, non-null values, time to compare them
|
||
|
switch {
|
||
|
case value1.Type().Is(String):
|
||
|
var s1, s2 string
|
||
|
err := value1.As(&s1)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("Error converting %s (value1) at %q: %w", value1, path, err)
|
||
|
}
|
||
|
err = value2.As(&s2)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("Error converting %s (value2) at %q: %w", value2, path, err)
|
||
|
}
|
||
|
if s1 != s2 {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: &value1,
|
||
|
Value2: &value2,
|
||
|
})
|
||
|
}
|
||
|
return false, nil
|
||
|
case value1.Type().Is(Number):
|
||
|
n1, n2 := big.NewFloat(0), big.NewFloat(0)
|
||
|
err := value1.As(&n1)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("Error converting %q: %w", path, err)
|
||
|
}
|
||
|
err = value2.As(&n2)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("Error converting %q: %w", path, err)
|
||
|
}
|
||
|
if n1.Cmp(n2) != 0 {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: &value1,
|
||
|
Value2: &value2,
|
||
|
})
|
||
|
}
|
||
|
return false, nil
|
||
|
case value1.Type().Is(Bool):
|
||
|
var b1, b2 bool
|
||
|
err := value1.As(&b1)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("Error converting %q: %w", path, err)
|
||
|
}
|
||
|
err = value2.As(&b2)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("Error converting %q: %w", path, err)
|
||
|
}
|
||
|
if b1 != b2 {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: &value1,
|
||
|
Value2: &value2,
|
||
|
})
|
||
|
}
|
||
|
return false, nil
|
||
|
case value1.Type().Is(List{}), value1.Type().Is(Set{}), value1.Type().Is(Tuple{}):
|
||
|
var s1, s2 []Value
|
||
|
err := value1.As(&s1)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("Error converting %q: %w", path, err)
|
||
|
}
|
||
|
err = value2.As(&s2)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("Error converting %q: %w", path, err)
|
||
|
}
|
||
|
// we only care about if the lengths match for lists,
|
||
|
// sets, and tuples. If any of the elements differ,
|
||
|
// the recursion of the walk will find them for us.
|
||
|
if len(s1) != len(s2) {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: &value1,
|
||
|
Value2: &value2,
|
||
|
})
|
||
|
return true, nil
|
||
|
}
|
||
|
return true, nil
|
||
|
case value1.Type().Is(Map{}), value1.Type().Is(Object{}):
|
||
|
m1 := map[string]Value{}
|
||
|
m2 := map[string]Value{}
|
||
|
err := value1.As(&m1)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("Error converting %q: %w", path, err)
|
||
|
}
|
||
|
err = value2.As(&m2)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("Error converting %q: %w", path, err)
|
||
|
}
|
||
|
// we need maps and objects to have the same exact keys
|
||
|
// as each other
|
||
|
if len(m1) != len(m2) {
|
||
|
diffs = append(diffs, ValueDiff{
|
||
|
Path: path,
|
||
|
Value1: &value1,
|
||
|
Value2: &value2,
|
||
|
})
|
||
|
return true, nil
|
||
|
}
|
||
|
// if we have the same keys, we can just let recursion
|
||
|
// from the walk check the sub-values match
|
||
|
return true, nil
|
||
|
case value1.Type().Is(DynamicPseudoType):
|
||
|
// Let recursion from the walk check the sub-values match
|
||
|
return true, nil
|
||
|
}
|
||
|
return false, fmt.Errorf("unexpected type %v in Diff at %s", value1.Type(), path)
|
||
|
})
|
||
|
return diffs, err
|
||
|
}
|