github.com/jhump/protoreflect@v1.16.0/dynamic/equal.go (about) 1 package dynamic 2 3 import ( 4 "bytes" 5 "reflect" 6 7 "github.com/golang/protobuf/proto" 8 9 "github.com/jhump/protoreflect/desc" 10 ) 11 12 // Equal returns true if the given two dynamic messages are equal. Two messages are equal when they 13 // have the same message type and same fields set to equal values. For proto3 messages, fields set 14 // to their zero value are considered unset. 15 func Equal(a, b *Message) bool { 16 if a == b { 17 return true 18 } 19 if (a == nil) != (b == nil) { 20 return false 21 } 22 if a.md.GetFullyQualifiedName() != b.md.GetFullyQualifiedName() { 23 return false 24 } 25 if len(a.values) != len(b.values) { 26 return false 27 } 28 if len(a.unknownFields) != len(b.unknownFields) { 29 return false 30 } 31 for tag, aval := range a.values { 32 bval, ok := b.values[tag] 33 if !ok { 34 return false 35 } 36 if !fieldsEqual(aval, bval) { 37 return false 38 } 39 } 40 for tag, au := range a.unknownFields { 41 bu, ok := b.unknownFields[tag] 42 if !ok { 43 return false 44 } 45 if len(au) != len(bu) { 46 return false 47 } 48 for i, aval := range au { 49 bval := bu[i] 50 if aval.Encoding != bval.Encoding { 51 return false 52 } 53 if aval.Encoding == proto.WireBytes || aval.Encoding == proto.WireStartGroup { 54 if !bytes.Equal(aval.Contents, bval.Contents) { 55 return false 56 } 57 } else if aval.Value != bval.Value { 58 return false 59 } 60 } 61 } 62 // all checks pass! 63 return true 64 } 65 66 func fieldsEqual(aval, bval interface{}) bool { 67 arv := reflect.ValueOf(aval) 68 brv := reflect.ValueOf(bval) 69 if arv.Type() != brv.Type() { 70 // it is possible that one is a dynamic message and one is not 71 apm, ok := aval.(proto.Message) 72 if !ok { 73 return false 74 } 75 bpm, ok := bval.(proto.Message) 76 if !ok { 77 return false 78 } 79 return MessagesEqual(apm, bpm) 80 81 } else { 82 switch arv.Kind() { 83 case reflect.Ptr: 84 apm, ok := aval.(proto.Message) 85 if !ok { 86 // Don't know how to compare pointer values that aren't messages! 87 // Maybe this should panic? 88 return false 89 } 90 bpm := bval.(proto.Message) // we know it will succeed because we know a and b have same type 91 return MessagesEqual(apm, bpm) 92 93 case reflect.Map: 94 return mapsEqual(arv, brv) 95 96 case reflect.Slice: 97 if arv.Type() == typeOfBytes { 98 return bytes.Equal(aval.([]byte), bval.([]byte)) 99 } else { 100 return slicesEqual(arv, brv) 101 } 102 103 default: 104 return aval == bval 105 } 106 } 107 } 108 109 func slicesEqual(a, b reflect.Value) bool { 110 if a.Len() != b.Len() { 111 return false 112 } 113 for i := 0; i < a.Len(); i++ { 114 ai := a.Index(i) 115 bi := b.Index(i) 116 if !fieldsEqual(ai.Interface(), bi.Interface()) { 117 return false 118 } 119 } 120 return true 121 } 122 123 // MessagesEqual returns true if the given two messages are equal. Use this instead of proto.Equal 124 // when one or both of the messages might be a dynamic message. 125 func MessagesEqual(a, b proto.Message) bool { 126 da, aok := a.(*Message) 127 db, bok := b.(*Message) 128 // Both dynamic messages 129 if aok && bok { 130 return Equal(da, db) 131 } 132 // Neither dynamic messages 133 if !aok && !bok { 134 return proto.Equal(a, b) 135 } 136 // Mixed 137 if bok { 138 // we want a to be the dynamic one 139 b, da = a, db 140 } 141 142 // Instead of panic'ing below if we have a nil dynamic message, check 143 // now and return false if the input message is not also nil. 144 if da == nil { 145 return isNil(b) 146 } 147 148 md, err := desc.LoadMessageDescriptorForMessage(b) 149 if err != nil { 150 return false 151 } 152 db = NewMessageWithMessageFactory(md, da.mf) 153 if db.ConvertFrom(b) != nil { 154 return false 155 } 156 return Equal(da, db) 157 }