github.com/hedzr/evendeep@v0.4.8/diff/diff.go (about)

     1  package diff
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  	"unsafe"
     8  
     9  	"github.com/hedzr/evendeep/dbglog"
    10  	"github.com/hedzr/evendeep/internal/natsort"
    11  	"github.com/hedzr/evendeep/internal/tool"
    12  	"github.com/hedzr/evendeep/typ"
    13  )
    14  
    15  // New compares two value deeply and returns the Diff of them.
    16  //
    17  // A Diff includes added, removed, and modified records, you can
    18  // PrettyPrint them for displaying.
    19  //
    20  //	delta, equal := evendeep.DeepDiff([]int{3, 0}, []int{9, 3, 0})
    21  //	t.Logf("delta: %v", delta)
    22  //	//        added: [2] = <zero>
    23  //	//        modified: [0] = 9 (int) (Old: 3)
    24  //	//        modified: [1] = 3 (int) (Old: <zero>)
    25  func New(lhs, rhs typ.Any, opts ...Opt) (inf Diff, equal bool) {
    26  	info1 := newInfo(opts...)
    27  	equal = info1.diff(lhs, rhs)
    28  	inf = info1
    29  	return
    30  }
    31  
    32  // Diff includes added, removed and modified records of the two values.
    33  type Diff interface {
    34  	ForAdded(fn func(key string, val typ.Any))
    35  	ForRemoved(fn func(key string, val typ.Any))
    36  	ForModified(fn func(key string, val Update))
    37  
    38  	PrettyPrint() string
    39  	String() string
    40  }
    41  
    42  func newInfo(opts ...Opt) *info {
    43  	inf := &info{
    44  		added:                    make(map[string]typ.Any),
    45  		removed:                  make(map[string]typ.Any),
    46  		modified:                 make(map[string]Update),
    47  		pathTable:                make(map[string]Path),
    48  		visited:                  make(map[visit]bool),
    49  		ignoredFields:            make(map[string]bool),
    50  		sliceNoOrder:             false,
    51  		stripPtr1st:              false,
    52  		treatEmptyStructPtrAsNil: false,
    53  		differentTypeStructs:     false,
    54  		compares: []Comparer{
    55  			&timeComparer{},
    56  			&bytesBufferComparer{},
    57  		},
    58  	}
    59  	for _, opt := range opts {
    60  		if opt != nil {
    61  			opt(inf)
    62  		}
    63  	}
    64  	return inf
    65  }
    66  
    67  type info struct {
    68  	added                    map[string]typ.Any
    69  	removed                  map[string]typ.Any
    70  	modified                 map[string]Update
    71  	pathTable                map[string]Path
    72  	visited                  map[visit]bool
    73  	ignoredFields            map[string]bool
    74  	sliceNoOrder             bool
    75  	stripPtr1st              bool
    76  	treatEmptyStructPtrAsNil bool
    77  	differentTypeStructs     bool
    78  	ignoreUnmatchedFields    bool
    79  	differentSizeArrays      bool
    80  	compares                 []Comparer
    81  }
    82  
    83  // - Comparer
    84  
    85  func (d *info) PutAdded(k string, v typ.Any)                { d.added[k] = v }
    86  func (d *info) PutRemoved(k string, v typ.Any)              { d.removed[k] = v }
    87  func (d *info) PutModified(k string, v Update)              { d.modified[k] = v }
    88  func (d *info) PutPath(path Path, parts ...PathPart) string { return d.mkkey(path, parts...) }
    89  
    90  // - Stringer
    91  
    92  func (d *info) String() string { return d.PrettyPrint() }
    93  
    94  // - Diff
    95  
    96  func (d *info) PrettyPrint() string {
    97  	var lines []string
    98  	if d != nil {
    99  		d.forMap(d.added, func(key string, val typ.Any) {
   100  			lines = append(lines, fmt.Sprintf("added: %s = %v\n", key, val))
   101  		})
   102  		for key, val := range d.modified {
   103  			if val.Old == nil { //nolint:gocritic // no need to switch to 'switch' clausev
   104  				lines = append(lines, fmt.Sprintf("modified: %s = %v (%v) (Old: nil)\n",
   105  					key, val.New, val.Typ))
   106  			} else if val.New == nil {
   107  				lines = append(lines, fmt.Sprintf("modified: %s = nil (Old: %v (%v))\n",
   108  					key, val.Old, val.Typ))
   109  			} else {
   110  				lines = append(lines, fmt.Sprintf("modified: %s = %v (%v) (Old: %v)\n",
   111  					key, val.New, val.Typ, val.Old))
   112  			}
   113  		}
   114  		d.forMap(d.removed, func(key string, val typ.Any) {
   115  			lines = append(lines, fmt.Sprintf("removed: %s = %v\n", key, val))
   116  		})
   117  	}
   118  
   119  	natsort.Strings(lines)
   120  	return strings.Join(lines, "")
   121  }
   122  
   123  func (d *info) ForAdded(fn func(key string, val typ.Any))   { d.forMap(d.added, fn) }
   124  func (d *info) ForRemoved(fn func(key string, val typ.Any)) { d.forMap(d.removed, fn) }
   125  func (d *info) ForModified(fn func(key string, val Update)) {
   126  	for k, v := range d.modified {
   127  		fn(k, v)
   128  	}
   129  }
   130  
   131  func (d *info) forMap(m map[string]typ.Any, fn func(key string, val typ.Any)) {
   132  	for k, v := range m {
   133  		fn(k, v)
   134  	}
   135  }
   136  
   137  // - Cloneable
   138  
   139  func (d *info) Clone() *info {
   140  	copym1 := func(m1 map[string]typ.Any) map[string]typ.Any {
   141  		m2 := make(map[string]typ.Any)
   142  		for k, v := range m1 {
   143  			m2[k] = v
   144  		}
   145  		return m2
   146  	}
   147  	copym2 := func(m1 map[string]Update) map[string]Update {
   148  		m2 := make(map[string]Update)
   149  		for k, v := range m1 {
   150  			m2[k] = v
   151  		}
   152  		return m2
   153  	}
   154  	copym3 := func(m1 map[string]Path) map[string]Path {
   155  		m2 := make(map[string]Path)
   156  		for k, v := range m1 {
   157  			m2[k] = v
   158  		}
   159  		return m2
   160  	}
   161  	copym4 := func(m1 map[visit]bool) map[visit]bool {
   162  		m2 := make(map[visit]bool)
   163  		for k, v := range m1 {
   164  			m2[k] = v
   165  		}
   166  		return m2
   167  	}
   168  	copym5 := func(m1 map[string]bool) map[string]bool {
   169  		m2 := make(map[string]bool)
   170  		for k, v := range m1 {
   171  			m2[k] = v
   172  		}
   173  		return m2
   174  	}
   175  	return &info{
   176  		added:         copym1(d.added),
   177  		removed:       copym1(d.removed),
   178  		modified:      copym2(d.modified),
   179  		pathTable:     copym3(d.pathTable),
   180  		visited:       copym4(d.visited),
   181  		ignoredFields: copym5(d.ignoredFields),
   182  		sliceNoOrder:  d.sliceNoOrder,
   183  	}
   184  }
   185  
   186  //
   187  
   188  func (d *info) mkkey(path Path, parts ...PathPart) (key string) {
   189  	dp := path.appendAndNew(parts...)
   190  	key = dp.String()
   191  	d.pathTable[key] = dp
   192  	return
   193  }
   194  
   195  func (d *info) diff(lhs, rhs typ.Any) bool {
   196  	lv, rv := reflect.ValueOf(lhs), reflect.ValueOf(rhs)
   197  	if d.stripPtr1st {
   198  		lv, rv = tool.Rdecodesimple(lv), tool.Rdecodesimple(rv)
   199  	} else {
   200  		_, lv = tool.Rskip(lv, reflect.Interface)
   201  		_, rv = tool.Rskip(rv, reflect.Interface)
   202  	}
   203  	var path Path
   204  	return d.diffv(lv, rv, path)
   205  }
   206  
   207  func (d *info) diffv(lv, rv reflect.Value, path Path) (equal bool) {
   208  	var processed bool
   209  
   210  	lvv, rvv := lv.IsValid(), rv.IsValid()
   211  	if equal, processed = d.testinvalid(lv, rv, lvv, rvv, path); processed {
   212  		dbglog.Log("  - Invalid object found: l = %v, r = %v, path = %v", tool.Valfmt(&lv), tool.Valfmtptr(&rv), path)
   213  		return
   214  	}
   215  
   216  	lvt, rvt := lv.Type(), rv.Type()
   217  	if lvt != rvt {
   218  		dbglog.Log("  - Unmatched type found: l = %v, r = %v, path = %v", tool.Typfmt(lvt), tool.Typfmt(rvt), path)
   219  		if d.differentTypeStructs && lv.Kind() == reflect.Struct && rv.Kind() == reflect.Struct {
   220  			return d.compareStructFields(lv, rv, path)
   221  		}
   222  		if d.differentSizeArrays && lv.Kind() == reflect.Array && rv.Kind() == reflect.Array {
   223  			return d.compareArrayDifferSizes(lv, rv, path)
   224  		}
   225  		d.PutModified(d.mkkey(path), Update{Old: tool.Valfmt(&lv), New: tool.Valfmt(&rv), Typ: tool.Typfmtvlite(&rv)})
   226  		return
   227  	}
   228  
   229  	var kind = lv.Kind()
   230  
   231  	if equal, processed = d.testvisited(lv, rv, lvt, path, kind); processed {
   232  		return
   233  	}
   234  
   235  	if equal, processed = d.testnil(lv, rv, lvt, path, kind); processed {
   236  		return
   237  	}
   238  
   239  	if equal, processed = d.testcomparer(lv, rv, lvt, path); processed {
   240  		return
   241  	}
   242  
   243  	return d.diffw(lv, rv, lvt, path, kind)
   244  }
   245  
   246  func (d *info) testinvalid(lv, rv reflect.Value, lvv, rvv bool, path Path) (equal, processed bool) {
   247  	if !lvv && !rvv {
   248  		return true, true
   249  	}
   250  
   251  	if !lvv {
   252  		d.PutModified(d.mkkey(path), Update{Old: nil, New: tool.Valfmt(&rv), Typ: tool.Typfmtvlite(&rv)})
   253  		return false, true
   254  	}
   255  	if !rvv {
   256  		d.PutModified(d.mkkey(path), Update{Old: tool.Valfmt(&lv), New: nil, Typ: tool.Typfmtvlite(&lv)})
   257  		return false, true
   258  	}
   259  	return
   260  }
   261  
   262  func (d *info) testvisited(lv, rv reflect.Value, typ1 reflect.Type, path Path,
   263  	kind reflect.Kind) (equal, processed bool) {
   264  	if lv.CanAddr() && rv.CanAddr() &&
   265  		tool.KindIs(kind, reflect.Array, reflect.Map, reflect.Slice, reflect.Struct) {
   266  		addr1 := unsafe.Pointer(lv.UnsafeAddr())
   267  		addr2 := unsafe.Pointer(rv.UnsafeAddr())
   268  		if uintptr(addr1) > uintptr(addr2) {
   269  			// Canonicalize order to reduce number of entries in visited.
   270  			// Assumes non-moving garbage collector.
   271  			addr1, addr2 = addr2, addr1
   272  		}
   273  
   274  		// Short circuit if references are already seen.
   275  		v := visit{addr1, addr2, typ1}
   276  		if d.visited[v] {
   277  			return true, true
   278  		}
   279  
   280  		// Remember for later.
   281  		d.visited[v] = true
   282  	}
   283  	return
   284  }
   285  
   286  func (d *info) testnil(lv, rv reflect.Value, typ1 reflect.Type, path Path, kind reflect.Kind) (equal, processed bool) {
   287  	switch kind { //nolint:exhaustive //no need
   288  	case reflect.Map, reflect.Ptr, reflect.Func, reflect.Chan, reflect.Slice:
   289  		ln, rn := tool.IsNil(lv), tool.IsNil(rv)
   290  		if ln && rn {
   291  			return true, true
   292  		}
   293  		if ln || rn {
   294  			if kind == reflect.Slice || kind == reflect.Map {
   295  				// le, re := tool.IsZero(lv), tool.IsZero(rv)
   296  				// if equal = le == re; equal {
   297  				// 	return true, true
   298  				// }
   299  
   300  				// By default, the nil array are equal to an empty array
   301  
   302  				// if d.treatEmptyStructPtrAsNil {
   303  				if (ln) && isEmptyObject(rv) {
   304  					return true, true
   305  				} else if (rn) && isEmptyObject(lv) {
   306  					return true, true
   307  				}
   308  				// }
   309  			} else if kind == reflect.Struct && d.treatEmptyStructPtrAsNil {
   310  				if ln && isEmptyStruct(rv) {
   311  					return true, true
   312  				} else if rn && isEmptyStruct(lv) {
   313  					return true, true
   314  				}
   315  			}
   316  			d.PutModified(d.mkkey(path), Update{Old: tool.Valfmt(&lv), New: tool.Valfmt(&rv), Typ: tool.Typfmtvlite(&lv)})
   317  			return false, true
   318  		}
   319  	}
   320  	return
   321  }
   322  
   323  func (d *info) testcomparer(lv, rv reflect.Value, typ1 reflect.Type,
   324  	path Path) (equal, processed bool) { //nolint:nonamedreturns //i do
   325  	var c Comparer
   326  	if c, processed = d.findComparer(typ1); processed {
   327  		equal = c.Equal(d, lv, rv, path)
   328  	}
   329  	return
   330  }
   331  
   332  func (d *info) findComparer(typ1 reflect.Type) (c Comparer, ok bool) {
   333  	for _, c = range d.compares {
   334  		if ok = c.Match(typ1); ok {
   335  			break
   336  		}
   337  	}
   338  	return
   339  }
   340  
   341  func (d *info) diffw(lv, rv reflect.Value, typ1 reflect.Type, path Path, kind reflect.Kind) (equal bool) {
   342  	switch kind { //nolint:exhaustive //no
   343  	case reflect.Array:
   344  		equal = d.diffArray(lv, rv, path)
   345  
   346  	case reflect.Slice:
   347  		if d.sliceNoOrder {
   348  			equal = d.diffSliceNoOrder(lv, rv, path)
   349  		} else {
   350  			equal = d.diffArray(lv, rv, path)
   351  		}
   352  
   353  	case reflect.Map:
   354  		equal = d.diffMap(lv, rv, path)
   355  
   356  	case reflect.Struct:
   357  		equal = d.diffStruct(lv, rv, typ1, path)
   358  
   359  	case reflect.Ptr:
   360  		equal = d.diffv(lv.Elem(), rv.Elem(), path)
   361  
   362  	case reflect.Interface:
   363  		equal = d.diffv(lv.Elem(), rv.Elem(), path)
   364  
   365  	case reflect.Chan:
   366  		equal = lv.Type() == rv.Type() && lv.Cap() == rv.Cap()
   367  
   368  	default:
   369  		a, b := lv.Interface(), rv.Interface()
   370  		if equal = reflect.DeepEqual(a, b); !equal {
   371  			d.PutModified(d.mkkey(path), Update{Old: tool.Valfmt(&lv), New: tool.Valfmt(&rv), Typ: tool.Typfmtvlite(&lv)})
   372  		}
   373  	}
   374  
   375  	return
   376  }
   377  
   378  func (d *info) diffArray(lv, rv reflect.Value, path Path) (equal bool) {
   379  	ll, rl := lv.Len(), rv.Len()
   380  	equal = true
   381  	for i := 0; i < tool.MinInt(ll, rl); i++ {
   382  		localPath := path.appendAndNew(sliceIndex(i))
   383  		aI, bI := lv.Index(i), rv.Index(i)
   384  		if eq := d.diffv(aI, bI, localPath); !eq {
   385  			dbglog.Log("    diffArray: [%d] not equal %v - %v", i, tool.Valfmt(&aI), tool.Valfmt(&bI))
   386  			equal = false
   387  		}
   388  	}
   389  	if ll > rl {
   390  		for i := rl; i < ll; i++ {
   391  			v := lv.Index(i)
   392  			if d.differentSizeArrays && tool.IsZero(v) {
   393  				continue
   394  			}
   395  			localPath := path.appendAndNew(sliceIndex(i))
   396  			d.PutRemoved(d.mkkey(localPath), tool.Valfmt(&v))
   397  			equal = false
   398  		}
   399  	} else if ll < rl {
   400  		for i := ll; i < rl; i++ {
   401  			v := rv.Index(i)
   402  			if d.differentSizeArrays && tool.IsZero(v) {
   403  				continue
   404  			}
   405  			localPath := path.appendAndNew(sliceIndex(i))
   406  			d.PutAdded(d.mkkey(localPath), tool.Valfmt(&v))
   407  			equal = false
   408  		}
   409  	}
   410  	return
   411  }
   412  
   413  func (d *info) diffSliceNoOrder(lv, rv reflect.Value, path Path) (equal bool) {
   414  	ll, rl := lv.Len(), rv.Len()
   415  	equal = true
   416  	m := make(map[int]bool)
   417  	for i := 0; i < tool.MinInt(ll, rl); i++ {
   418  		localPath := path.appendAndNew(sliceIndex(i))
   419  		lvit := lv.Index(i)
   420  		var eq bool
   421  		for j := 0; j < rl; j++ {
   422  			if eq = d.Clone().diffv(lvit, rv.Index(j), localPath); eq {
   423  				m[j] = true
   424  				break
   425  			}
   426  		}
   427  		if !eq {
   428  			d.PutRemoved(d.mkkey(localPath), tool.Valfmt(&lvit))
   429  			equal = false
   430  		}
   431  	}
   432  	for i := 0; i < rl; i++ {
   433  		localPath := path.appendAndNew(sliceIndex(i))
   434  		if _, ok := m[i]; ok {
   435  			continue
   436  		}
   437  		rvit := rv.Index(i)
   438  		d.PutAdded(d.mkkey(localPath), tool.Valfmt(&rvit))
   439  		equal = false
   440  	}
   441  	return
   442  }
   443  
   444  func (d *info) diffMap(lv, rv reflect.Value, path Path) (equal bool) {
   445  	equal = true
   446  	for _, key := range lv.MapKeys() {
   447  		aI, bI := lv.MapIndex(key), rv.MapIndex(key)
   448  		localPath := path.appendAndNew(mapKey{key.Interface()})
   449  		if !bI.IsValid() {
   450  			d.PutRemoved(d.mkkey(localPath), tool.Valfmt(&aI))
   451  			equal = false
   452  		} else if eq := d.diffv(aI, bI, localPath); !eq {
   453  			equal = false
   454  		}
   455  	}
   456  	for _, key := range rv.MapKeys() {
   457  		aI := lv.MapIndex(key)
   458  		if !aI.IsValid() {
   459  			bI := rv.MapIndex(key)
   460  			localPath := path.appendAndNew(mapKey{key.Interface()})
   461  			d.PutAdded(d.mkkey(localPath), tool.Valfmt(&bI))
   462  			equal = false
   463  		}
   464  	}
   465  	return
   466  }
   467  
   468  func (d *info) diffStruct(lv, rv reflect.Value, typ1 reflect.Type, path Path) (equal bool) {
   469  	equal = true
   470  	for i := 0; i < typ1.NumField(); i++ {
   471  		index := []int{i}
   472  		field := typ1.FieldByIndex(index)
   473  		if vk := field.Tag.Get("diff"); vk == "ignore" || vk == "-" { // skip fields marked to be ignored
   474  			continue
   475  		}
   476  		if _, skip := d.ignoredFields[field.Name]; skip {
   477  			continue
   478  		}
   479  		localPath := path.appendAndNew(structField(field.Name))
   480  		aI := tool.UnsafeReflectValue(lv.FieldByIndex(index))
   481  		bI := tool.UnsafeReflectValue(rv.FieldByIndex(index))
   482  		if d.treatEmptyStructPtrAsNil && field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
   483  			ln, rn := tool.IsNil(aI), tool.IsNil(bI)
   484  			var eq bool
   485  			if eq = ln && rn; !equal {
   486  				if eq = ln && isEmptyStruct(bI.Elem()); !eq {
   487  					eq = rn && isEmptyStruct(aI.Elem())
   488  				}
   489  			}
   490  			if !eq {
   491  				equal = false
   492  				d.PutModified(d.mkkey(path), Update{Old: tool.Valfmt(&aI), New: tool.Valfmt(&bI), Typ: tool.Typfmtvlite(&aI)})
   493  			}
   494  			continue
   495  		}
   496  		if eq := d.diffv(aI, bI, localPath); !eq {
   497  			equal = false
   498  		}
   499  	}
   500  	return
   501  }
   502  
   503  func (d *info) compareArrayDifferSizes(lv, rv reflect.Value, path Path) (equal bool) {
   504  	return d.diffArray(lv, rv, path)
   505  }
   506  
   507  func (d *info) compareStructFields(lv, rv reflect.Value, path Path) (equal bool) {
   508  	for i, lt, rt := 0, lv.Type(), rv.Type(); i < lv.NumField(); i++ {
   509  		// fldval := lv.Field(i)
   510  		fldtyp := lt.Field(i)
   511  		fldname := fldtyp.Name
   512  		if _, ok := rt.FieldByName(fldname); ok {
   513  			l := lv.Field(i)
   514  			r := rv.FieldByName(fldname)
   515  			localPath := path.appendAndNew(structField(fldname))
   516  			equal = d.diffv(l, r, localPath)
   517  			if !equal {
   518  				if equal = !l.IsValid() && !r.IsValid(); !equal {
   519  					return
   520  				} // if both l and r are invalid, assumes its equivalence
   521  			}
   522  		} else if !d.ignoreUnmatchedFields {
   523  			return false
   524  		}
   525  	}
   526  	return
   527  }