158 lines
3.4 KiB
Go
158 lines
3.4 KiB
Go
package dynamic
|
|
|
|
import (
|
|
"bytes"
|
|
"reflect"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
"github.com/jhump/protoreflect/desc"
|
|
)
|
|
|
|
// Equal returns true if the given two dynamic messages are equal. Two messages are equal when they
|
|
// have the same message type and same fields set to equal values. For proto3 messages, fields set
|
|
// to their zero value are considered unset.
|
|
func Equal(a, b *Message) bool {
|
|
if a == b {
|
|
return true
|
|
}
|
|
if (a == nil) != (b == nil) {
|
|
return false
|
|
}
|
|
if a.md.GetFullyQualifiedName() != b.md.GetFullyQualifiedName() {
|
|
return false
|
|
}
|
|
if len(a.values) != len(b.values) {
|
|
return false
|
|
}
|
|
if len(a.unknownFields) != len(b.unknownFields) {
|
|
return false
|
|
}
|
|
for tag, aval := range a.values {
|
|
bval, ok := b.values[tag]
|
|
if !ok {
|
|
return false
|
|
}
|
|
if !fieldsEqual(aval, bval) {
|
|
return false
|
|
}
|
|
}
|
|
for tag, au := range a.unknownFields {
|
|
bu, ok := b.unknownFields[tag]
|
|
if !ok {
|
|
return false
|
|
}
|
|
if len(au) != len(bu) {
|
|
return false
|
|
}
|
|
for i, aval := range au {
|
|
bval := bu[i]
|
|
if aval.Encoding != bval.Encoding {
|
|
return false
|
|
}
|
|
if aval.Encoding == proto.WireBytes || aval.Encoding == proto.WireStartGroup {
|
|
if !bytes.Equal(aval.Contents, bval.Contents) {
|
|
return false
|
|
}
|
|
} else if aval.Value != bval.Value {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
// all checks pass!
|
|
return true
|
|
}
|
|
|
|
func fieldsEqual(aval, bval interface{}) bool {
|
|
arv := reflect.ValueOf(aval)
|
|
brv := reflect.ValueOf(bval)
|
|
if arv.Type() != brv.Type() {
|
|
// it is possible that one is a dynamic message and one is not
|
|
apm, ok := aval.(proto.Message)
|
|
if !ok {
|
|
return false
|
|
}
|
|
bpm, ok := bval.(proto.Message)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return MessagesEqual(apm, bpm)
|
|
|
|
} else {
|
|
switch arv.Kind() {
|
|
case reflect.Ptr:
|
|
apm, ok := aval.(proto.Message)
|
|
if !ok {
|
|
// Don't know how to compare pointer values that aren't messages!
|
|
// Maybe this should panic?
|
|
return false
|
|
}
|
|
bpm := bval.(proto.Message) // we know it will succeed because we know a and b have same type
|
|
return MessagesEqual(apm, bpm)
|
|
|
|
case reflect.Map:
|
|
return mapsEqual(arv, brv)
|
|
|
|
case reflect.Slice:
|
|
if arv.Type() == typeOfBytes {
|
|
return bytes.Equal(aval.([]byte), bval.([]byte))
|
|
} else {
|
|
return slicesEqual(arv, brv)
|
|
}
|
|
|
|
default:
|
|
return aval == bval
|
|
}
|
|
}
|
|
}
|
|
|
|
func slicesEqual(a, b reflect.Value) bool {
|
|
if a.Len() != b.Len() {
|
|
return false
|
|
}
|
|
for i := 0; i < a.Len(); i++ {
|
|
ai := a.Index(i)
|
|
bi := b.Index(i)
|
|
if !fieldsEqual(ai.Interface(), bi.Interface()) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// MessagesEqual returns true if the given two messages are equal. Use this instead of proto.Equal
|
|
// when one or both of the messages might be a dynamic message.
|
|
func MessagesEqual(a, b proto.Message) bool {
|
|
da, aok := a.(*Message)
|
|
db, bok := b.(*Message)
|
|
// Both dynamic messages
|
|
if aok && bok {
|
|
return Equal(da, db)
|
|
}
|
|
// Neither dynamic messages
|
|
if !aok && !bok {
|
|
return proto.Equal(a, b)
|
|
}
|
|
// Mixed
|
|
if bok {
|
|
// we want a to be the dynamic one
|
|
b, da = a, db
|
|
}
|
|
|
|
// Instead of panic'ing below if we have a nil dynamic message, check
|
|
// now and return false if the input message is not also nil.
|
|
if da == nil {
|
|
return isNil(b)
|
|
}
|
|
|
|
md, err := desc.LoadMessageDescriptorForMessage(b)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
db = NewMessageWithMessageFactory(md, da.mf)
|
|
if db.ConvertFrom(b) != nil {
|
|
return false
|
|
}
|
|
return Equal(da, db)
|
|
}
|