diff --git a/any/any.go b/anyutil/any.go similarity index 60% rename from any/any.go rename to anyutil/any.go index c02d94a..82aefee 100644 --- a/any/any.go +++ b/anyutil/any.go @@ -28,11 +28,18 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package any +package anyutil import ( + "fmt" + "strings" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protodesc" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + "google.golang.org/protobuf/types/dynamicpb" "google.golang.org/protobuf/types/known/anypb" ) @@ -61,3 +68,45 @@ func MarshalFrom(dst *anypb.Any, src proto.Message, opts proto.MarshalOptions) e dst.Value = b return nil } + +// Unpack unpacks the message inside an any, first using the provided +// typeResolver (defaults to protoregistry.GlobalTypes), and if that fails, +// then using the provided fileResolver (defaults to protoregistry.GlobalFiles) +// with dynamicpb. +func Unpack(any *anypb.Any, fileResolver protodesc.Resolver, typeResolver protoregistry.MessageTypeResolver) (proto.Message, error) { + if typeResolver == nil { + typeResolver = protoregistry.GlobalTypes + } + + url := any.TypeUrl + typ, err := typeResolver.FindMessageByURL(url) + if err == protoregistry.NotFound { + if fileResolver == nil { + fileResolver = protoregistry.GlobalFiles + } + + // If the proto v2 registry doesn't have this message, then we use + // protoFiles (which can e.g. be initialized to gogo's MergedRegistry) + // to retrieve the message descriptor, and then use dynamicpb on that + // message descriptor to create a proto.Message + typeURL := strings.TrimPrefix(any.TypeUrl, "/") + + msgDesc, err := fileResolver.FindDescriptorByName(protoreflect.FullName(typeURL)) + if err != nil { + return nil, fmt.Errorf("protoFiles does not have descriptor %s: %w", any.TypeUrl, err) + } + + typ = dynamicpb.NewMessageType(msgDesc.(protoreflect.MessageDescriptor)) + + } else if err != nil { + return nil, err + } + + packedMsg := typ.New().Interface() + err = any.UnmarshalTo(packedMsg) + if err != nil { + return nil, fmt.Errorf("cannot unmarshal msg %s: %w", any.TypeUrl, err) + } + + return packedMsg, nil +} diff --git a/any/any_test.go b/anyutil/any_test.go similarity index 52% rename from any/any_test.go rename to anyutil/any_test.go index 6d4c57f..8db292b 100644 --- a/any/any_test.go +++ b/anyutil/any_test.go @@ -1,4 +1,4 @@ -package any_test +package anyutil_test import ( "testing" @@ -6,10 +6,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/anypb" - "github.com/cosmos/cosmos-proto/any" + "github.com/cosmos/cosmos-proto/anyutil" "github.com/cosmos/cosmos-proto/testpb" ) @@ -17,11 +18,11 @@ func TestAny(t *testing.T) { value := &testpb.A{SomeBoolean: true} dst1 := &anypb.Any{} - err := any.MarshalFrom(dst1, value, proto.MarshalOptions{}) + err := anyutil.MarshalFrom(dst1, value, proto.MarshalOptions{}) require.NoError(t, err) require.Equal(t, "/A", dst1.TypeUrl) // Make sure there's no "type.googleapis.com/" prefix. - dst2, err := any.New(value) + dst2, err := anyutil.New(value) require.NoError(t, err) require.Equal(t, "/A", dst2.TypeUrl) // Make sure there's no "type.googleapis.com/" prefix. @@ -31,3 +32,20 @@ func TestAny(t *testing.T) { diff := cmp.Diff(value, newValue, protocmp.Transform()) require.Empty(t, diff) } + +func TestUnpack(t *testing.T) { + value := &testpb.A{SomeBoolean: true} + any, err := anyutil.New(value) + require.NoError(t, err) + + msg, err := anyutil.Unpack(any, nil, nil) + require.NoError(t, err) + diff := cmp.Diff(value, msg, protocmp.Transform()) + require.Empty(t, diff) + + // Test the same thing with using the dynamicpb path. + msg, err = anyutil.Unpack(any, protoregistry.GlobalFiles, &protoregistry.Types{}) + require.NoError(t, err) + diff = cmp.Diff(value, msg, protocmp.Transform()) + require.Empty(t, diff) +} diff --git a/any/doc.go b/anyutil/doc.go similarity index 98% rename from any/doc.go rename to anyutil/doc.go index be040ac..08c58f7 100644 --- a/any/doc.go +++ b/anyutil/doc.go @@ -16,4 +16,4 @@ // // This package exposes the `New` and `MarshalFrom` helper functions, which do // not prepend any prefix to type URLs. -package any +package anyutil