| 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 |
| } |