blob: ce727fd57a714eb20ecae2de84a03cdf4655601e [file] [log] [blame]
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -07001package dynamic
2
3import (
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).
17func 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.
35func 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
60func 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}