2022-03-01 10:10:29 -06:00
package cmputil
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp"
)
type DiffReport [ ] Diff
// GetDiffsForField returns subset of the diffs which path starts with the provided path
func ( r DiffReport ) GetDiffsForField ( path string ) DiffReport {
var result [ ] Diff
for _ , diff := range r {
2022-06-06 22:16:05 -05:00
if strings . HasPrefix ( diff . Path , path ) {
if diff . Path != path {
char := [ ] rune ( diff . Path ) [ len ( path ) ]
if char != '.' && char != '[' { // if the following symbol is not a delimiter or bracket then that's not our path
continue
}
}
2022-03-01 10:10:29 -06:00
result = append ( result , diff )
}
}
return result
}
// DiffReporter is a simple custom reporter that only records differences
// detected during comparison. Implements an interface required by cmp.Reporter option
type DiffReporter struct {
path cmp . Path
Diffs DiffReport
}
func ( r * DiffReporter ) PushStep ( ps cmp . PathStep ) {
r . path = append ( r . path , ps )
}
func ( r * DiffReporter ) PopStep ( ) {
r . path = r . path [ : len ( r . path ) - 1 ]
}
func ( r * DiffReporter ) Report ( rs cmp . Result ) {
if ! rs . Equal ( ) {
vx , vy := r . path . Last ( ) . Values ( )
r . Diffs = append ( r . Diffs , Diff {
Path : printPath ( r . path ) ,
Left : vx ,
Right : vy ,
} )
}
}
func printPath ( p cmp . Path ) string {
ss := strings . Builder { }
for _ , s := range p {
toAdd := ""
switch v := s . ( type ) {
case cmp . StructField :
toAdd = v . String ( )
case cmp . MapIndex :
toAdd = fmt . Sprintf ( "[%s]" , v . Key ( ) )
case cmp . SliceIndex :
if v . Key ( ) >= 0 {
toAdd = fmt . Sprintf ( "[%d]" , v . Key ( ) )
}
}
if toAdd == "" {
continue
}
ss . WriteString ( toAdd )
}
return strings . TrimPrefix ( ss . String ( ) , "." )
}
func ( r DiffReport ) String ( ) string {
b := strings . Builder { }
for _ , diff := range r {
b . WriteString ( diff . String ( ) )
b . WriteByte ( '\n' )
}
return b . String ( )
}
2022-05-06 10:06:00 -05:00
// Paths returns the slice of paths of the current DiffReport
func ( r DiffReport ) Paths ( ) [ ] string {
2023-06-28 17:08:05 -05:00
var result = make ( [ ] string , 0 , len ( r ) )
2022-05-06 10:06:00 -05:00
for _ , diff := range r {
result = append ( result , diff . Path )
}
return result
}
2022-03-01 10:10:29 -06:00
type Diff struct {
// Path to the field that has difference separated by period. Array index and key are designated by square brackets.
// For example, Annotations[12345].Data.Fields[0].ID
Path string
Left reflect . Value
Right reflect . Value
}
func ( d * Diff ) String ( ) string {
2022-05-06 10:06:00 -05:00
return fmt . Sprintf ( "%v:\n\t-: %+v\n\t+: %+v\n" , d . Path , describeReflectValue ( d . Left ) , describeReflectValue ( d . Right ) )
}
2023-08-30 10:46:47 -05:00
func describeReflectValue ( v reflect . Value ) any {
2022-03-01 10:10:29 -06:00
// invalid reflect.Value is produced when two collections (slices\maps) are compared and one misses value.
// This way go-cmp indicates that an element was added\removed from a list.
2022-05-06 10:06:00 -05:00
if ! v . IsValid ( ) {
return "<none>"
2022-03-01 10:10:29 -06:00
}
2022-05-06 10:06:00 -05:00
return v
}
// IsAddOperation returns true when
2022-09-12 05:03:49 -05:00
// - Left does not have value and Right has
// - the kind of Left and Right is either reflect.Slice or reflect.Map and the length of Left is less than length of Right
//
2022-05-06 10:06:00 -05:00
// In all other cases it returns false.
// NOTE: this is applicable to diff of Maps and Slices only
func ( d * Diff ) IsAddOperation ( ) bool {
return ! d . Left . IsValid ( ) && d . Right . IsValid ( ) || ( d . Left . Kind ( ) == d . Right . Kind ( ) && // cmp reports adding first element to a nil slice as creation of one and therefore Left is valid and it is nil and right is a new slice
( d . Left . Kind ( ) == reflect . Slice || d . Left . Kind ( ) == reflect . Map ) && d . Left . Len ( ) < d . Right . Len ( ) )
}
// IsDeleteOperation returns true when
2022-09-12 05:03:49 -05:00
// - Right does not have value and Left has
// - the kind of Left and Right is either reflect.Slice or reflect.Map and the length of Right is less than length of Left
//
2022-05-06 10:06:00 -05:00
// In all other cases it returns false.
// NOTE: this is applicable to diff of Maps and Slices only
func ( d * Diff ) IsDeleteOperation ( ) bool {
return d . Left . IsValid ( ) && ! d . Right . IsValid ( ) || ( d . Left . Kind ( ) == d . Right . Kind ( ) &&
( d . Left . Kind ( ) == reflect . Slice || d . Left . Kind ( ) == reflect . Map ) && d . Left . Len ( ) > d . Right . Len ( ) )
2022-03-01 10:10:29 -06:00
}