101 lines
2.6 KiB
Go
101 lines
2.6 KiB
Go
|
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
|
||
|
}
|