| 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.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 aok { |
| 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) |
| } else { |
| md, err := desc.LoadMessageDescriptorForMessage(a) |
| if err != nil { |
| return false |
| } |
| da = NewMessageWithMessageFactory(md, db.mf) |
| if da.ConvertFrom(a) != nil { |
| return false |
| } |
| return Equal(da, db) |
| } |
| } |