pulumi/pkg/resource/properties_diff.go

269 lines
7.1 KiB
Go

// Copyright 2016 Pulumi, Inc. All rights reserved.
package resource
import (
"sort"
)
// ObjectDiff holds the results of diffing two object property maps.
type ObjectDiff struct {
Adds PropertyMap // properties in this map are created in the new.
Deletes PropertyMap // properties in this map are deleted from the new.
Sames PropertyMap // properties in this map are the same.
Updates map[PropertyKey]ValueDiff // properties in this map are changed in the new.
}
// Added returns true if the property 'k' has been added in the new property set.
func (diff *ObjectDiff) Added(k PropertyKey) bool {
_, has := diff.Adds[k]
return has
}
// Deleted returns true if the property 'k' has been deleted from the new property set.
func (diff *ObjectDiff) Deleted(k PropertyKey) bool {
_, has := diff.Deletes[k]
return has
}
// Updated returns true if the property 'k' has been changed between new and old property sets.
func (diff *ObjectDiff) Updated(k PropertyKey) bool {
_, has := diff.Updates[k]
return has
}
// Changed returns true if the property 'k' is known to be different between old and new.
func (diff *ObjectDiff) Changed(k PropertyKey) bool {
return diff.Added(k) || diff.Deleted(k) || diff.Updated(k)
}
// Same returns true if the property 'k' is *not* known to be different; note that this isn't the same as looking up in
// the Sames map, because it is possible the key is simply missing altogether (as is the case for nulls).
func (diff *ObjectDiff) Same(k PropertyKey) bool {
return !diff.Changed(k)
}
// Keys returns a stable snapshot of all keys known to this object, across adds, deletes, sames, and updates.
func (diff *ObjectDiff) Keys() []PropertyKey {
var ks propertyKeys
for k := range diff.Adds {
ks = append(ks, k)
}
for k := range diff.Deletes {
ks = append(ks, k)
}
for k := range diff.Sames {
ks = append(ks, k)
}
for k := range diff.Updates {
ks = append(ks, k)
}
sort.Sort(ks)
return ks
}
// ValueDiff holds the results of diffing two property values.
type ValueDiff struct {
Old PropertyValue // the old value.
New PropertyValue // the new value.
Array *ArrayDiff // the array's detailed diffs (only for arrays).
Object *ObjectDiff // the object's detailed diffs (only for objects).
}
// ArrayDiff holds the results of diffing two arrays of property values.
type ArrayDiff struct {
Adds map[int]PropertyValue // elements added in the new.
Deletes map[int]PropertyValue // elements deleted in the new.
Sames map[int]PropertyValue // elements the same in both.
Updates map[int]ValueDiff // elements that have changed in the new.
}
// Len computes the length of this array, taking into account adds, deletes, sames, and updates.
func (diff *ArrayDiff) Len() int {
len := 0
for i := range diff.Adds {
if i+1 > len {
len = i + 1
}
}
for i := range diff.Deletes {
if i+1 > len {
len = i + 1
}
}
for i := range diff.Sames {
if i+1 > len {
len = i + 1
}
}
for i := range diff.Updates {
if i+1 > len {
len = i + 1
}
}
return len
}
// Diff returns a diffset by comparing the property map to another; it returns nil if there are no diffs.
func (props PropertyMap) Diff(other PropertyMap) *ObjectDiff {
adds := make(PropertyMap)
deletes := make(PropertyMap)
sames := make(PropertyMap)
updates := make(map[PropertyKey]ValueDiff)
// First find any updates or deletes.
for k, old := range props {
if new, has := other[k]; has {
if diff := old.Diff(new); diff != nil {
if old.IsNull() {
adds[k] = new
} else if new.IsNull() {
deletes[k] = old
} else {
updates[k] = *diff
}
} else {
sames[k] = old
}
} else if !new.IsNull() {
deletes[k] = old
}
}
// Next find any additions not in the old map.
for k, new := range other {
if _, has := props[k]; !has && !new.IsNull() {
adds[k] = new
}
}
// If no diffs were found, return nil; else return a diff structure.
if len(adds) == 0 && len(deletes) == 0 && len(updates) == 0 {
return nil
}
return &ObjectDiff{
Adds: adds,
Deletes: deletes,
Sames: sames,
Updates: updates,
}
}
// Diff returns a diff by comparing a single property value to another; it returns nil if there are no diffs.
func (v PropertyValue) Diff(other PropertyValue) *ValueDiff {
if v.IsArray() && other.IsArray() {
old := v.ArrayValue()
new := other.ArrayValue()
// If any elements exist in the new array but not the old, track them as adds.
adds := make(map[int]PropertyValue)
for i := len(old); i < len(new); i++ {
adds[i] = new[i]
}
// If any elements exist in the old array but not the new, track them as adds.
deletes := make(map[int]PropertyValue)
for i := len(new); i < len(old); i++ {
deletes[i] = old[i]
}
// Now if elements exist in both, track them as sames or updates.
sames := make(map[int]PropertyValue)
updates := make(map[int]ValueDiff)
for i := 0; i < len(old) && i < len(new); i++ {
if diff := old[i].Diff(new[i]); diff != nil {
updates[i] = *diff
} else {
sames[i] = old[i]
}
}
if len(adds) == 0 && len(deletes) == 0 && len(updates) == 0 {
return nil
}
return &ValueDiff{
Old: v,
New: other,
Array: &ArrayDiff{
Adds: adds,
Deletes: deletes,
Sames: sames,
Updates: updates,
},
}
}
if v.IsObject() && other.IsObject() {
old := v.ObjectValue()
new := other.ObjectValue()
if diff := old.Diff(new); diff != nil {
return &ValueDiff{
Old: v,
New: other,
Object: diff,
}
}
return nil
}
// If we got here, either the values are primitives, or they weren't the same type; do a simple diff.
if v.V == other.V {
return nil
}
return &ValueDiff{Old: v, New: other}
}
// DeepEquals returns true if this property map is deeply equal to the other property map; and false otherwise.
func (props PropertyMap) DeepEquals(other PropertyMap) bool {
// If any in props either doesn't exist, or is of a different value, return false.
for _, k := range StablePropertyKeys(props) {
v := props[k]
if p, has := other[k]; has {
if !v.DeepEquals(p) {
return false
}
} else if !v.IsNull() {
return false
}
}
// If the other map has properties that this map doesn't have, return false.
for _, k := range StablePropertyKeys(other) {
if _, has := props[k]; !has && !other[k].IsNull() {
return false
}
}
return true
}
// DeepEquals returns true if this property map is deeply equal to the other property map; and false otherwise.
func (v PropertyValue) DeepEquals(other PropertyValue) bool {
// Arrays are equal if they are both of the same size and elements are deeply equal.
if v.IsArray() {
if !other.IsArray() {
return false
}
va := v.ArrayValue()
oa := other.ArrayValue()
if len(va) != len(oa) {
return false
}
for i, elem := range va {
if !elem.DeepEquals(oa[i]) {
return false
}
}
return true
}
// Object values are equal if their contents are deeply equal.
if v.IsObject() {
if !other.IsObject() {
return false
}
vo := v.ObjectValue()
oa := other.ObjectValue()
return vo.DeepEquals(oa)
}
// For all other cases, primitives are equal if their values are equal.
return v.V == other.V
}