github.com/jxskiss/gopkg@v0.17.3/reflectx/equality.go (about)

     1  package reflectx
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  )
     8  
     9  // IsIdenticalType checks whether the given two object types have same
    10  // struct fields and memory layout (same order, same name and same type).
    11  // It's useful to check generated types are exactly same in different
    12  // packages, e.g. Thrift, Protobuf, Msgpack, etc.
    13  //
    14  // If two types are identical, it is expected that unsafe pointer casting
    15  // between the two types won't crash the program.
    16  // If the given two types are not identical, the returned diff message
    17  // contains the detail difference.
    18  func IsIdenticalType(a, b interface{}) (equal bool, diff string) {
    19  	typ1 := reflect.TypeOf(a)
    20  	typ2 := reflect.TypeOf(b)
    21  	return newStrictTypecmp().isEqualType(typ1, typ2)
    22  }
    23  
    24  // IsIdenticalThriftType checks whether the given two object types have same
    25  // struct fields and memory layout, in case that a field's name does not
    26  // match, but the thrift tag's first two fields match, it also considers
    27  // the field matches.
    28  //
    29  // It is almost same with IsIdenticalType, but helps the situation that
    30  // different Thrift generators which generate different field names.
    31  //
    32  // If two types are identical, it is expected that unsafe pointer casting
    33  // between the two types won't crash the program.
    34  // If the given two types are not identical, the returned diff message
    35  // contains the detail difference.
    36  func IsIdenticalThriftType(a, b interface{}) (equal bool, diff string) {
    37  	typ1 := reflect.TypeOf(a)
    38  	typ2 := reflect.TypeOf(b)
    39  	return newThriftTypecmp().isEqualType(typ1, typ2)
    40  }
    41  
    42  type typ1typ2 struct {
    43  	typ1, typ2 reflect.Type
    44  }
    45  
    46  const (
    47  	notequal = 1
    48  	isequal  = 2
    49  	checking = 3
    50  )
    51  
    52  type cmpresult struct {
    53  	result int
    54  	diff   string
    55  }
    56  
    57  type typecmp struct {
    58  	seen map[typ1typ2]*cmpresult
    59  
    60  	fieldCmp func(typ reflect.Type, f1, f2 reflect.StructField) (bool, string)
    61  }
    62  
    63  func newStrictTypecmp() *typecmp {
    64  	cmp := &typecmp{
    65  		seen: make(map[typ1typ2]*cmpresult),
    66  	}
    67  	cmp.fieldCmp = cmp.isStrictEqualField
    68  	return cmp
    69  }
    70  
    71  func newThriftTypecmp() *typecmp {
    72  	cmp := &typecmp{
    73  		seen: make(map[typ1typ2]*cmpresult),
    74  	}
    75  	cmp.fieldCmp = cmp.isEqualThriftField
    76  	return cmp
    77  }
    78  
    79  func (p *typecmp) isEqualType(typ1, typ2 reflect.Type) (bool, string) {
    80  	if typ1.Kind() != typ2.Kind() {
    81  		return false, fmt.Sprintf("type kind not equal: %s, %s", _t(typ1), _t(typ2))
    82  	}
    83  	if typ1.Kind() == reflect.Ptr {
    84  		if typ1.Elem().Kind() != typ2.Elem().Kind() {
    85  			return false, fmt.Sprintf("pointer type not equal: %s, %s", _t(typ1), _t(typ2))
    86  		}
    87  		typ1 = typ1.Elem()
    88  		typ2 = typ2.Elem()
    89  		return p.isEqualType(typ1, typ2)
    90  	}
    91  	switch typ1.Kind() {
    92  	case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
    93  		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
    94  		reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String:
    95  		return true, ""
    96  	case reflect.Struct:
    97  		return p.isEqualStruct(typ1, typ2)
    98  	case reflect.Slice:
    99  		return p.isEqualSlice(typ1, typ2)
   100  	case reflect.Map:
   101  		return p.isEqualMap(typ1, typ2)
   102  	}
   103  	return false, fmt.Sprintf("unsupported types: %s, %s", _t(typ1), _t(typ2))
   104  }
   105  
   106  func (p *typecmp) isEqualStruct(typ1, typ2 reflect.Type) (bool, string) {
   107  	if typ1.Kind() != reflect.Struct || typ1.Kind() != reflect.Struct {
   108  		return false, fmt.Sprintf("type is not struct: %s, %s", _t(typ1), _t(typ2))
   109  	}
   110  	typidx := typ1typ2{typ1, typ2}
   111  	if cmpr := p.seen[typidx]; cmpr != nil {
   112  
   113  		// In case of recursive type, cmpr.result will be checking here,
   114  		// we treat it as equal, the final result will be updated below.
   115  		return cmpr.result != notequal, cmpr.diff
   116  	}
   117  
   118  	p.seen[typidx] = &cmpresult{checking, ""}
   119  	if typ1.NumField() != typ2.NumField() {
   120  		diff := fmt.Sprintf("struct field num not match: %s, %s", _t(typ1), _t(typ2))
   121  		p.seen[typidx] = &cmpresult{notequal, diff}
   122  		return false, diff
   123  	}
   124  	fnum := typ1.NumField()
   125  	for i := 0; i < fnum; i++ {
   126  		f1 := typ1.Field(i)
   127  		f2 := typ2.Field(i)
   128  		if equal, diff := p.fieldCmp(typ1, f1, f2); !equal {
   129  			diff = fmt.Sprintf("struct field not equal: %s", diff)
   130  			p.seen[typidx] = &cmpresult{notequal, diff}
   131  			return false, diff
   132  		}
   133  	}
   134  	p.seen[typidx] = &cmpresult{isequal, ""}
   135  	return true, ""
   136  }
   137  
   138  func (p *typecmp) isStrictEqualField(typ reflect.Type, field1, field2 reflect.StructField) (bool, string) {
   139  	if field1.Name != field2.Name {
   140  		return false, fmt.Sprintf("field name not equal: %s, %s", _f(typ, field1), _f(typ, field2))
   141  	}
   142  	if field1.Offset != field2.Offset {
   143  		return false, fmt.Sprintf("field offset not equal: %s, %s", _f(typ, field1), _f(typ, field2))
   144  	}
   145  
   146  	typ1 := field1.Type
   147  	typ2 := field2.Type
   148  	if typ1.Kind() != typ2.Kind() {
   149  		return false, fmt.Sprintf("field type not equal: %s, %s", _f(typ, field1), _f(typ, field2))
   150  	}
   151  	if typ1.Kind() == reflect.Ptr {
   152  		typ1 = typ1.Elem()
   153  		typ2 = typ2.Elem()
   154  	}
   155  	equal, diff := p.isEqualType(typ1, typ2)
   156  	if equal {
   157  		return true, ""
   158  	}
   159  	return false, fmt.Sprintf("field type not euqal: %s", diff)
   160  }
   161  
   162  func (p *typecmp) isEqualThriftField(typ reflect.Type, field1, field2 reflect.StructField) (bool, string) {
   163  	if field1.Name != field2.Name {
   164  		tag1 := field1.Tag.Get("thrift")
   165  		tag2 := field2.Tag.Get("thrift")
   166  		if !isEqualThriftTag(tag1, tag2) {
   167  			return false, fmt.Sprintf("field name and thrift tag both not equal: %s, %s", _f(typ, field1), _f(typ, field2))
   168  		}
   169  	}
   170  	if field1.Offset != field2.Offset {
   171  		return false, fmt.Sprintf("field offset not equal: %s, %s", _f(typ, field1), _f(typ, field2))
   172  	}
   173  
   174  	typ1 := field1.Type
   175  	typ2 := field2.Type
   176  	if typ1.Kind() != typ2.Kind() {
   177  		return false, fmt.Sprintf("field type not equal: %s, %s", _f(typ, field1), _f(typ, field2))
   178  	}
   179  	if typ1.Kind() == reflect.Ptr {
   180  		typ1 = typ1.Elem()
   181  		typ2 = typ2.Elem()
   182  	}
   183  	equal, diff := p.isEqualType(typ1, typ2)
   184  	if equal {
   185  		return true, ""
   186  	}
   187  	return false, fmt.Sprintf("field type not euqal: %s", diff)
   188  }
   189  
   190  func isEqualThriftTag(tag1, tag2 string) bool {
   191  	if tag1 == "" || tag2 == "" {
   192  		return false
   193  	}
   194  	if tag1 == tag2 {
   195  		return true
   196  	}
   197  	parts1 := strings.Split(tag1, ",")
   198  	parts2 := strings.Split(tag2, ",")
   199  	if len(parts1) >= 2 && len(parts2) >= 2 &&
   200  		parts1[0] == parts2[0] && // parts[0] is the field's name
   201  		parts1[1] == parts2[1] { // parts[1] is the field's id number
   202  		return true
   203  	}
   204  	return false
   205  }
   206  
   207  func (p *typecmp) isEqualSlice(typ1, typ2 reflect.Type) (bool, string) {
   208  	if typ1.Kind() != reflect.Slice || typ1.Kind() != reflect.Slice {
   209  		return false, fmt.Sprintf("type is not slice: %s, %s", _t(typ1), _t(typ2))
   210  	}
   211  	typ1 = typ1.Elem()
   212  	typ2 = typ2.Elem()
   213  	return p.isEqualType(typ1, typ2)
   214  }
   215  
   216  func (p *typecmp) isEqualMap(typ1, typ2 reflect.Type) (bool, string) {
   217  	if typ1.Kind() != reflect.Map || typ2.Kind() != reflect.Map {
   218  		return false, fmt.Sprintf("type is not map: %s, %s", _t(typ1), _t(typ2))
   219  	}
   220  
   221  	keyTyp1 := typ1.Key()
   222  	keyTyp2 := typ2.Key()
   223  	if equal, diff := p.isEqualType(keyTyp1, keyTyp2); !equal {
   224  		return false, fmt.Sprintf("map key: %s", diff)
   225  	}
   226  
   227  	elemTyp1 := typ1.Elem()
   228  	elemTyp2 := typ2.Elem()
   229  	if equal, diff := p.isEqualType(elemTyp1, elemTyp2); !equal {
   230  		return false, fmt.Sprintf("map value: %s", diff)
   231  	}
   232  
   233  	return true, ""
   234  }
   235  
   236  func _t(typ reflect.Type) string {
   237  	return fmt.Sprintf("%s.%s", typ.PkgPath(), typ.Name())
   238  }
   239  
   240  func _f(typ reflect.Type, field reflect.StructField) string {
   241  	return fmt.Sprintf("%s.%s", _t(typ), field.Name)
   242  }