terraform-provider-gitea/vendor/github.com/hashicorp/terraform-plugin-go/tftypes/diff.go
Tobias Trabelsi e1266ebf64
Some checks reported errors
continuous-integration/drone/pr Build encountered an error
continuous-integration/drone/push Build encountered an error
updated GHA
Update to v2 SDK
updated dependencies
2022-08-06 16:21:18 +02:00

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
}