cosmos-sdk/codec/types/interface_registry.go

279 lines
8.4 KiB
Go

package types
import (
"fmt"
"reflect"
"github.com/gogo/protobuf/jsonpb"
"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
jsonpb.AnyResolver
// 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.base.v1beta1.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)
// ListAllInterfaces list the type URLs of all registered interfaces.
ListAllInterfaces() []string
// ListImplementations lists the valid type URLs for the given interface name that can be used
// for the provided interface type URL.
ListImplementations(ifaceTypeURL string) []string
}
// 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 AnyInterface
// // 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
typeURLMap map[string]reflect.Type
}
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{},
typeURLMap: map[string]reflect.Type{},
}
}
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...)
}
// RegisterImplementations registers a concrete proto Message which implements
// the given interface.
//
// This function PANICs if different concrete types are registered under the
// same typeURL.
func (registry *interfaceRegistry) RegisterImplementations(iface interface{}, impls ...proto.Message) {
for _, impl := range impls {
typeURL := "/" + proto.MessageName(impl)
registry.registerImpl(iface, typeURL, impl)
}
}
// RegisterCustomTypeURL registers a concrete type which implements the given
// interface under `typeURL`.
//
// This function PANICs if different concrete types are registered under the
// same typeURL.
func (registry *interfaceRegistry) RegisterCustomTypeURL(iface interface{}, typeURL string, impl proto.Message) {
registry.registerImpl(iface, typeURL, impl)
}
// registerImpl registers a concrete type which implements the given
// interface under `typeURL`.
//
// This function PANICs if different concrete types are registered under the
// same typeURL.
func (registry *interfaceRegistry) registerImpl(iface interface{}, typeURL string, impl proto.Message) {
ityp := reflect.TypeOf(iface).Elem()
imap, found := registry.interfaceImpls[ityp]
if !found {
imap = map[string]reflect.Type{}
}
implType := reflect.TypeOf(impl)
if !implType.AssignableTo(ityp) {
panic(fmt.Errorf("type %T doesn't actually implement interface %+v", impl, ityp))
}
// Check if we already registered something under the given typeURL. It's
// okay to register the same concrete type again, but if we are registering
// a new concrete type under the same typeURL, then we throw an error (here,
// we panic).
foundImplType, found := imap[typeURL]
if found && foundImplType != implType {
panic(
fmt.Errorf(
"concrete type %s has already been registered under typeURL %s, cannot register %s under same typeURL. "+
"This usually means that there are conflicting modules registering different concrete types "+
"for a same interface implementation",
foundImplType,
typeURL,
implType,
),
)
}
imap[typeURL] = implType
registry.typeURLMap[typeURL] = implType
registry.interfaceImpls[ityp] = imap
}
func (registry *interfaceRegistry) ListAllInterfaces() []string {
interfaceNames := registry.interfaceNames
keys := make([]string, 0, len(interfaceNames))
for key := range interfaceNames {
keys = append(keys, key)
}
return keys
}
func (registry *interfaceRegistry) ListImplementations(ifaceName string) []string {
typ, ok := registry.interfaceNames[ifaceName]
if !ok {
return []string{}
}
impls, ok := registry.interfaceImpls[typ.Elem()]
if !ok {
return []string{}
}
keys := make([]string, 0, len(impls))
for key := range impls {
keys = append(keys, key)
}
return keys
}
func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error {
// here we gracefully handle the case in which `any` itself is `nil`, which may occur in message decoding
if any == nil {
return nil
}
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
}
// Resolve returns the proto message given its typeURL. It works with types
// registered with RegisterInterface/RegisterImplementations, as well as those
// registered with RegisterWithCustomTypeURL.
func (registry *interfaceRegistry) Resolve(typeURL string) (proto.Message, error) {
typ, found := registry.typeURLMap[typeURL]
if !found {
return nil, fmt.Errorf("unable to resolve type URL %s", typeURL)
}
msg, ok := reflect.New(typ.Elem()).Interface().(proto.Message)
if !ok {
return nil, fmt.Errorf("can't resolve type URL %s", typeURL)
}
return msg, 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
}