Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 1 | package dynamic |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "reflect" |
| 6 | |
| 7 | "github.com/golang/protobuf/proto" |
| 8 | |
| 9 | "github.com/jhump/protoreflect/desc" |
| 10 | ) |
| 11 | |
| 12 | // Merge merges the given source message into the given destination message. Use |
| 13 | // use this instead of proto.Merge when one or both of the messages might be a |
| 14 | // a dynamic message. If there is a problem merging the messages, such as the |
| 15 | // two messages having different types, then this method will panic (just as |
| 16 | // proto.Merges does). |
| 17 | func Merge(dst, src proto.Message) { |
| 18 | if dm, ok := dst.(*Message); ok { |
| 19 | if err := dm.MergeFrom(src); err != nil { |
| 20 | panic(err.Error()) |
| 21 | } |
| 22 | } else if dm, ok := src.(*Message); ok { |
| 23 | if err := dm.MergeInto(dst); err != nil { |
| 24 | panic(err.Error()) |
| 25 | } |
| 26 | } else { |
| 27 | proto.Merge(dst, src) |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | // TryMerge merges the given source message into the given destination message. |
| 32 | // You can use this instead of proto.Merge when one or both of the messages |
| 33 | // might be a dynamic message. Unlike proto.Merge, this method will return an |
| 34 | // error on failure instead of panic'ing. |
| 35 | func TryMerge(dst, src proto.Message) error { |
| 36 | if dm, ok := dst.(*Message); ok { |
| 37 | if err := dm.MergeFrom(src); err != nil { |
| 38 | return err |
| 39 | } |
| 40 | } else if dm, ok := src.(*Message); ok { |
| 41 | if err := dm.MergeInto(dst); err != nil { |
| 42 | return err |
| 43 | } |
| 44 | } else { |
| 45 | // proto.Merge panics on bad input, so we first verify |
| 46 | // inputs and return error instead of panic |
| 47 | out := reflect.ValueOf(dst) |
| 48 | if out.IsNil() { |
| 49 | return errors.New("proto: nil destination") |
| 50 | } |
| 51 | in := reflect.ValueOf(src) |
| 52 | if in.Type() != out.Type() { |
| 53 | return errors.New("proto: type mismatch") |
| 54 | } |
| 55 | proto.Merge(dst, src) |
| 56 | } |
| 57 | return nil |
| 58 | } |
| 59 | |
| 60 | func mergeField(m *Message, fd *desc.FieldDescriptor, val interface{}) error { |
| 61 | rv := reflect.ValueOf(val) |
| 62 | |
| 63 | if fd.IsMap() && rv.Kind() == reflect.Map { |
| 64 | return mergeMapField(m, fd, rv) |
| 65 | } |
| 66 | |
| 67 | if fd.IsRepeated() && rv.Kind() == reflect.Slice && rv.Type() != typeOfBytes { |
| 68 | for i := 0; i < rv.Len(); i++ { |
| 69 | e := rv.Index(i) |
| 70 | if e.Kind() == reflect.Interface && !e.IsNil() { |
| 71 | e = e.Elem() |
| 72 | } |
| 73 | if err := m.addRepeatedField(fd, e.Interface()); err != nil { |
| 74 | return err |
| 75 | } |
| 76 | } |
| 77 | return nil |
| 78 | } |
| 79 | |
| 80 | if fd.IsRepeated() { |
| 81 | return m.addRepeatedField(fd, val) |
| 82 | } else if fd.GetMessageType() == nil { |
| 83 | return m.setField(fd, val) |
| 84 | } |
| 85 | |
| 86 | // it's a message type, so we want to merge contents |
| 87 | var err error |
| 88 | if val, err = validFieldValue(fd, val); err != nil { |
| 89 | return err |
| 90 | } |
| 91 | |
| 92 | existing, _ := m.doGetField(fd, true) |
| 93 | if existing != nil && !reflect.ValueOf(existing).IsNil() { |
| 94 | return TryMerge(existing.(proto.Message), val.(proto.Message)) |
| 95 | } |
| 96 | |
| 97 | // no existing message, so just set field |
| 98 | m.internalSetField(fd, val) |
| 99 | return nil |
| 100 | } |