blob: ce727fd57a714eb20ecae2de84a03cdf4655601e [file] [log] [blame]
package dynamic
import (
"errors"
"reflect"
"github.com/golang/protobuf/proto"
"github.com/jhump/protoreflect/desc"
)
// Merge merges the given source message into the given destination message. Use
// use this instead of proto.Merge when one or both of the messages might be a
// a dynamic message. If there is a problem merging the messages, such as the
// two messages having different types, then this method will panic (just as
// proto.Merges does).
func Merge(dst, src proto.Message) {
if dm, ok := dst.(*Message); ok {
if err := dm.MergeFrom(src); err != nil {
panic(err.Error())
}
} else if dm, ok := src.(*Message); ok {
if err := dm.MergeInto(dst); err != nil {
panic(err.Error())
}
} else {
proto.Merge(dst, src)
}
}
// TryMerge merges the given source message into the given destination message.
// You can use this instead of proto.Merge when one or both of the messages
// might be a dynamic message. Unlike proto.Merge, this method will return an
// error on failure instead of panic'ing.
func TryMerge(dst, src proto.Message) error {
if dm, ok := dst.(*Message); ok {
if err := dm.MergeFrom(src); err != nil {
return err
}
} else if dm, ok := src.(*Message); ok {
if err := dm.MergeInto(dst); err != nil {
return err
}
} else {
// proto.Merge panics on bad input, so we first verify
// inputs and return error instead of panic
out := reflect.ValueOf(dst)
if out.IsNil() {
return errors.New("proto: nil destination")
}
in := reflect.ValueOf(src)
if in.Type() != out.Type() {
return errors.New("proto: type mismatch")
}
proto.Merge(dst, src)
}
return nil
}
func mergeField(m *Message, fd *desc.FieldDescriptor, val interface{}) error {
rv := reflect.ValueOf(val)
if fd.IsMap() && rv.Kind() == reflect.Map {
return mergeMapField(m, fd, rv)
}
if fd.IsRepeated() && rv.Kind() == reflect.Slice && rv.Type() != typeOfBytes {
for i := 0; i < rv.Len(); i++ {
e := rv.Index(i)
if e.Kind() == reflect.Interface && !e.IsNil() {
e = e.Elem()
}
if err := m.addRepeatedField(fd, e.Interface()); err != nil {
return err
}
}
return nil
}
if fd.IsRepeated() {
return m.addRepeatedField(fd, val)
} else if fd.GetMessageType() == nil {
return m.setField(fd, val)
}
// it's a message type, so we want to merge contents
var err error
if val, err = validFieldValue(fd, val); err != nil {
return err
}
existing, _ := m.doGetField(fd, true)
if existing != nil && !reflect.ValueOf(existing).IsNil() {
return TryMerge(existing.(proto.Message), val.(proto.Message))
}
// no existing message, so just set field
m.internalSetField(fd, val)
return nil
}