cosmos-sdk/codec/types/interface_registry.go

175 lines
5.1 KiB
Go

package types
import (
"fmt"
"reflect"
"github.com/gogo/protobuf/proto"
)
// AnyUnpacker is an interface which allows safely unpacking types packed
// in Any's against a whitelist of registered types
type AnyUnpacker interface {
// UnpackAny unpacks the value in any to the interface pointer passed in as
// iface. Note that the type in any must have been registered in the
// underlying whitelist registry as a concrete type for that interface
// Ex:
// var msg sdk.Msg
// err := cdc.UnpackAny(any, &msg)
// ...
UnpackAny(any *Any, iface interface{}) error
}
// InterfaceRegistry provides a mechanism for registering interfaces and
// implementations that can be safely unpacked from Any
type InterfaceRegistry interface {
AnyUnpacker
// RegisterInterface associates protoName as the public name for the
// interface passed in as iface. This is to be used primarily to create
// a public facing registry of interface implementations for clients.
// protoName should be a well-chosen public facing name that remains stable.
// RegisterInterface takes an optional list of impls to be registered
// as implementations of iface.
//
// Ex:
// registry.RegisterInterface("cosmos_sdk.Msg", (*sdk.Msg)(nil))
RegisterInterface(protoName string, iface interface{}, impls ...proto.Message)
// RegisterImplementations registers impls as concrete implementations of
// the interface iface.
//
// Ex:
// registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSend{}, &MsgMultiSend{})
RegisterImplementations(iface interface{}, impls ...proto.Message)
}
// UnpackInterfacesMessage is meant to extend protobuf types (which implement
// proto.Message) to support a post-deserialization phase which unpacks
// types packed within Any's using the whitelist provided by AnyUnpacker
type UnpackInterfacesMessage interface {
// UnpackInterfaces is implemented in order to unpack values packed within
// Any's using the AnyUnpacker. It should generally be implemented as
// follows:
// func (s *MyStruct) UnpackInterfaces(unpacker AnyUnpacker) error {
// var x AnInterface
// // where X is an Any field on MyStruct
// err := unpacker.UnpackAny(s.X, &x)
// if err != nil {
// return nil
// }
// // where Y is a field on MyStruct that implements UnpackInterfacesMessage itself
// err = s.Y.UnpackInterfaces(unpacker)
// if err != nil {
// return nil
// }
// return nil
// }
UnpackInterfaces(unpacker AnyUnpacker) error
}
type interfaceRegistry struct {
interfaceNames map[string]reflect.Type
interfaceImpls map[reflect.Type]interfaceMap
}
type interfaceMap = map[string]reflect.Type
// NewInterfaceRegistry returns a new InterfaceRegistry
func NewInterfaceRegistry() InterfaceRegistry {
return &interfaceRegistry{
interfaceNames: map[string]reflect.Type{},
interfaceImpls: map[reflect.Type]interfaceMap{},
}
}
func (registry *interfaceRegistry) RegisterInterface(protoName string, iface interface{}, impls ...proto.Message) {
typ := reflect.TypeOf(iface)
if typ.Elem().Kind() != reflect.Interface {
panic(fmt.Errorf("%T is not an interface type", iface))
}
registry.interfaceNames[protoName] = typ
registry.RegisterImplementations(iface, impls...)
}
func (registry *interfaceRegistry) RegisterImplementations(iface interface{}, impls ...proto.Message) {
ityp := reflect.TypeOf(iface).Elem()
imap, found := registry.interfaceImpls[ityp]
if !found {
imap = map[string]reflect.Type{}
}
for _, impl := range impls {
implType := reflect.TypeOf(impl)
if !implType.AssignableTo(ityp) {
panic(fmt.Errorf("type %T doesn't actually implement interface %+v", impl, ityp))
}
imap["/"+proto.MessageName(impl)] = implType
}
registry.interfaceImpls[ityp] = imap
}
func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error {
if any.TypeUrl == "" {
// if TypeUrl is empty return nil because without it we can't actually unpack anything
return nil
}
rv := reflect.ValueOf(iface)
if rv.Kind() != reflect.Ptr {
return fmt.Errorf("UnpackAny expects a pointer")
}
rt := rv.Elem().Type()
cachedValue := any.cachedValue
if cachedValue != nil {
if reflect.TypeOf(cachedValue).AssignableTo(rt) {
rv.Elem().Set(reflect.ValueOf(cachedValue))
return nil
}
}
imap, found := registry.interfaceImpls[rt]
if !found {
return fmt.Errorf("no registered implementations of type %+v", rt)
}
typ, found := imap[any.TypeUrl]
if !found {
return fmt.Errorf("no concrete type registered for type URL %s against interface %T", any.TypeUrl, iface)
}
msg, ok := reflect.New(typ.Elem()).Interface().(proto.Message)
if !ok {
return fmt.Errorf("can't proto unmarshal %T", msg)
}
err := proto.Unmarshal(any.Value, msg)
if err != nil {
return err
}
err = UnpackInterfaces(msg, registry)
if err != nil {
return err
}
rv.Elem().Set(reflect.ValueOf(msg))
any.cachedValue = msg
return nil
}
// UnpackInterfaces is a convenience function that calls UnpackInterfaces
// on x if x implements UnpackInterfacesMessage
func UnpackInterfaces(x interface{}, unpacker AnyUnpacker) error {
if msg, ok := x.(UnpackInterfacesMessage); ok {
return msg.UnpackInterfaces(unpacker)
}
return nil
}