encoding/textpb: unmarshal Any

Also fix marshaling Any in expanded form to contain the correct type_url
value.

Change-Id: I4b467e74bb1d73255effd9cc4cfff9cf4558940f
Reviewed-on: https://go-review.googlesource.com/c/156342
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/encoding/textpb/decode.go b/encoding/textpb/decode.go
index f7b10ad..f906e18 100644
--- a/encoding/textpb/decode.go
+++ b/encoding/textpb/decode.go
@@ -8,10 +8,12 @@
 	"fmt"
 	"strings"
 
+	protoV1 "github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/v2/internal/encoding/text"
 	"github.com/golang/protobuf/v2/internal/errors"
 	"github.com/golang/protobuf/v2/internal/pragma"
 	"github.com/golang/protobuf/v2/internal/set"
+	pvalue "github.com/golang/protobuf/v2/internal/value"
 	"github.com/golang/protobuf/v2/proto"
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
 	"github.com/golang/protobuf/v2/reflect/protoregistry"
@@ -87,9 +89,15 @@
 	var nerr errors.NonFatal
 
 	msgType := m.Type()
+	knownFields := m.KnownFields()
+
+	// Handle expanded Any message.
+	if msgType.FullName() == "google.protobuf.Any" && isExpandedAny(tmsg) {
+		return o.unmarshalAny(tmsg[0], knownFields)
+	}
+
 	fieldDescs := msgType.Fields()
 	reservedNames := msgType.ReservedNames()
-	knownFields := m.KnownFields()
 	xtTypes := knownFields.ExtensionTypes()
 	var reqNums set.Ints
 	var seenNums set.Ints
@@ -109,18 +117,21 @@
 				fd = fieldDescs.ByName(pref.Name(strings.ToLower(string(name))))
 			}
 		case text.String:
-			// TODO: Handle Any expansions here as well.
-
-			// Handle extensions. Extensions have to be registered first in the message's
+			// Handle extensions only. This code path is not for Any.
+			if msgType.FullName() == "google.protobuf.Any" {
+				break
+			}
+			// Extensions have to be registered first in the message's
 			// ExtensionTypes before setting a value to it.
 			xtName := pref.FullName(tkey.String())
-			// Check first if it is already registered. This is the case for repeated fields.
+			// Check first if it is already registered. This is the case for
+			// repeated fields.
 			xt := xtTypes.ByName(xtName)
 			if xt == nil {
 				var err error
 				xt, err = o.Resolver.FindExtensionByName(xtName)
 				if err != nil && err != protoregistry.NotFound {
-					return err
+					return errors.New("unable to resolve [%v]: %v", xtName, err)
 				}
 				if xt != nil {
 					xtTypes.Register(xt)
@@ -274,12 +285,11 @@
 		// If input is int32, use directly.
 		if n, ok := input.Int(b32); ok {
 			return pref.ValueOf(pref.EnumNumber(n)), nil
-		} else {
-			if name, ok := input.Name(); ok {
-				// Lookup EnumNumber based on name.
-				if enumVal := fd.EnumType().Values().ByName(name); enumVal != nil {
-					return pref.ValueOf(enumVal.Number()), nil
-				}
+		}
+		if name, ok := input.Name(); ok {
+			// Lookup EnumNumber based on name.
+			if enumVal := fd.EnumType().Values().ByName(name); enumVal != nil {
+				return pref.ValueOf(enumVal.Number()), nil
 			}
 		}
 	default:
@@ -327,7 +337,7 @@
 
 	// Determine ahead whether map entry is a scalar type or a message type in order to call the
 	// appropriate unmarshalMapValue func inside the for loop below.
-	unmarshalMapValue := o.unmarshalMapScalarValue
+	unmarshalMapValue := unmarshalMapScalarValue
 	switch valDesc.Kind() {
 	case pref.MessageKind, pref.GroupKind:
 		unmarshalMapValue = o.unmarshalMapMessageValue
@@ -418,7 +428,7 @@
 
 // unmarshalMapScalarValue unmarshals given scalar-type text.Value into a protoreflect.Map
 // for the given MapKey.
-func (o UnmarshalOptions) unmarshalMapScalarValue(input text.Value, pkey pref.MapKey, fd pref.FieldDescriptor, mmap pref.Map) error {
+func unmarshalMapScalarValue(input text.Value, pkey pref.MapKey, fd pref.FieldDescriptor, mmap pref.Map) error {
 	var val pref.Value
 	if input.Type() == 0 {
 		val = fd.Default()
@@ -432,3 +442,51 @@
 	mmap.Set(pkey, val)
 	return nil
 }
+
+// isExpandedAny returns true if given [][2]text.Value may be an expanded Any that contains only one
+// field with key type of text.String type and value type of text.Message.
+func isExpandedAny(tmsg [][2]text.Value) bool {
+	if len(tmsg) != 1 {
+		return false
+	}
+
+	field := tmsg[0]
+	return field[0].Type() == text.String && field[1].Type() == text.Message
+}
+
+// unmarshalAny unmarshals an expanded Any textproto. This method assumes that the given
+// tfield has key type of text.String and value type of text.Message.
+func (o UnmarshalOptions) unmarshalAny(tfield [2]text.Value, knownFields pref.KnownFields) error {
+	var nerr errors.NonFatal
+
+	typeURL := tfield[0].String()
+	value := tfield[1].Message()
+
+	mt, err := o.Resolver.FindMessageByURL(typeURL)
+	if !nerr.Merge(err) {
+		return errors.New("unable to resolve message [%v]: %v", typeURL, err)
+	}
+	// Create new message for the embedded message type and unmarshal the
+	// value into it.
+	m := mt.New()
+	if err := o.unmarshalMessage(value, m); !nerr.Merge(err) {
+		return err
+	}
+	// Serialize the embedded message and assign the resulting bytes to the value field.
+	// TODO: Switch to V2 marshal and enable deterministic option when ready.
+	var mv1 protoV1.Message
+	if mtmp, ok := m.(pvalue.Unwrapper); ok {
+		mv1 = mtmp.ProtoUnwrap().(protoV1.Message)
+	} else {
+		mv1 = m.Interface().(protoV1.Message)
+	}
+	b, err := protoV1.Marshal(mv1)
+	if !nerr.Merge(err) {
+		return err
+	}
+
+	knownFields.Set(pref.FieldNumber(1), pref.ValueOf(typeURL))
+	knownFields.Set(pref.FieldNumber(2), pref.ValueOf(b))
+
+	return nerr.E
+}
diff --git a/encoding/textpb/decode_test.go b/encoding/textpb/decode_test.go
index 9911362..90a9e78 100644
--- a/encoding/textpb/decode_test.go
+++ b/encoding/textpb/decode_test.go
@@ -10,6 +10,7 @@
 
 	protoV1 "github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/protoapi"
+	anypb "github.com/golang/protobuf/ptypes/any"
 	"github.com/golang/protobuf/v2/encoding/textpb"
 	"github.com/golang/protobuf/v2/internal/legacy"
 	"github.com/golang/protobuf/v2/internal/scalar"
@@ -49,6 +50,7 @@
 func TestUnmarshal(t *testing.T) {
 	tests := []struct {
 		desc         string
+		umo          textpb.UnmarshalOptions
 		inputMessage proto.Message
 		inputText    string
 		wantMessage  proto.Message
@@ -1122,13 +1124,186 @@
 		inputMessage: &pb2.Extensions{},
 		inputText:    "[pb2.invalid_message_field]: true",
 		wantErr:      true,
+	}, {
+		desc:         "Any not expanded",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `opt_any: {
+type_url: "pb2.Nested"
+value: "some bytes"
+}
+`,
+		wantMessage: &pb2.KnownTypes{
+			OptAny: &anypb.Any{
+				TypeUrl: "pb2.Nested",
+				Value:   []byte("some bytes"),
+			},
+		},
+	}, {
+		desc:         "Any not expanded missing value",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `opt_any: {
+type_url: "pb2.Nested"
+}
+`,
+		wantMessage: &pb2.KnownTypes{
+			OptAny: &anypb.Any{
+				TypeUrl: "pb2.Nested",
+			},
+		},
+	}, {
+		desc:         "Any not expanded missing type_url",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `opt_any: {
+value: "some bytes"
+}
+`,
+		wantMessage: &pb2.KnownTypes{
+			OptAny: &anypb.Any{
+				Value: []byte("some bytes"),
+			},
+		},
+	}, {
+		desc: "Any expanded",
+		umo: func() textpb.UnmarshalOptions {
+			m := &pb2.Nested{}
+			resolver := preg.NewTypes(m.ProtoReflect().Type())
+			return textpb.UnmarshalOptions{Resolver: resolver}
+		}(),
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `opt_any: {
+  [foobar/pb2.Nested]: {
+    opt_string: "embedded inside Any"
+    opt_nested: {
+      opt_string: "inception"
+    }
+  }
+}
+`,
+		wantMessage: func() proto.Message {
+			m := &pb2.Nested{
+				OptString: scalar.String("embedded inside Any"),
+				OptNested: &pb2.Nested{
+					OptString: scalar.String("inception"),
+				},
+			}
+			// TODO: Switch to V2 marshal when ready.
+			b, err := protoV1.Marshal(m)
+			if err != nil {
+				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
+			}
+			return &pb2.KnownTypes{
+				OptAny: &anypb.Any{
+					TypeUrl: "foobar/pb2.Nested",
+					Value:   b,
+				},
+			}
+		}(),
+	}, {
+		desc: "Any expanded with empty value",
+		umo: func() textpb.UnmarshalOptions {
+			m := &pb2.Nested{}
+			resolver := preg.NewTypes(m.ProtoReflect().Type())
+			return textpb.UnmarshalOptions{Resolver: resolver}
+		}(),
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `opt_any: {
+[foo.com/pb2.Nested]: {}
+}
+`,
+		wantMessage: &pb2.KnownTypes{
+			OptAny: &anypb.Any{
+				TypeUrl: "foo.com/pb2.Nested",
+			},
+		},
+	}, {
+		desc: "Any expanded with missing required error",
+		umo: func() textpb.UnmarshalOptions {
+			m := &pb2.PartialRequired{}
+			resolver := preg.NewTypes(m.ProtoReflect().Type())
+			return textpb.UnmarshalOptions{Resolver: resolver}
+		}(),
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `opt_any: {
+  [pb2.PartialRequired]: {
+    opt_string: "embedded inside Any"
+  }
+}
+`,
+		wantMessage: func() proto.Message {
+			m := &pb2.PartialRequired{
+				OptString: scalar.String("embedded inside Any"),
+			}
+			// TODO: Switch to V2 marshal when ready.
+			b, err := protoV1.Marshal(m)
+			// Ignore required not set error.
+			if _, ok := err.(*protoV1.RequiredNotSetError); !ok {
+				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
+			}
+			return &pb2.KnownTypes{
+				OptAny: &anypb.Any{
+					TypeUrl: "pb2.PartialRequired",
+					Value:   b,
+				},
+			}
+		}(),
+		wantErr: true,
+	}, {
+		desc:         "Any expanded with unregistered type",
+		umo:          textpb.UnmarshalOptions{Resolver: preg.NewTypes()},
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `opt_any: {
+[SomeMessage]: {}
+}
+`,
+		wantErr: true,
+	}, {
+		desc: "Any expanded with invalid value",
+		umo: func() textpb.UnmarshalOptions {
+			m := &pb2.Nested{}
+			resolver := preg.NewTypes(m.ProtoReflect().Type())
+			return textpb.UnmarshalOptions{Resolver: resolver}
+		}(),
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `opt_any: {
+[pb2.Nested]: 123
+}
+`,
+		wantErr: true,
+	}, {
+		desc: "Any expanded with unknown fields",
+		umo: func() textpb.UnmarshalOptions {
+			m := &pb2.Nested{}
+			resolver := preg.NewTypes(m.ProtoReflect().Type())
+			return textpb.UnmarshalOptions{Resolver: resolver}
+		}(),
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `opt_any: {
+[pb2.Nested]: {}
+unknown: ""
+}
+`,
+		wantErr: true,
+	}, {
+		desc: "Any contains expanded and unexpanded fields",
+		umo: func() textpb.UnmarshalOptions {
+			m := &pb2.Nested{}
+			resolver := preg.NewTypes(m.ProtoReflect().Type())
+			return textpb.UnmarshalOptions{Resolver: resolver}
+		}(),
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `opt_any: {
+[pb2.Nested]: {}
+type_url: "pb2.Nested"
+}
+`,
+		wantErr: true,
 	}}
 
 	for _, tt := range tests {
 		tt := tt
 		t.Run(tt.desc, func(t *testing.T) {
 			t.Parallel()
-			err := textpb.Unmarshal(tt.inputMessage, []byte(tt.inputText))
+			err := tt.umo.Unmarshal(tt.inputMessage, []byte(tt.inputText))
 			if err != nil && !tt.wantErr {
 				t.Errorf("Unmarshal() returned error: %v\n\n", err)
 			}
diff --git a/encoding/textpb/encode.go b/encoding/textpb/encode.go
index 57a35c6..77606da 100644
--- a/encoding/textpb/encode.go
+++ b/encoding/textpb/encode.go
@@ -375,10 +375,10 @@
 	}
 
 	knownFields := m.KnownFields()
-	typeURL := knownFields.Get(tfd.Number())
+	typeURL := knownFields.Get(tfd.Number()).String()
 	value := knownFields.Get(vfd.Number())
 
-	emt, err := o.Resolver.FindMessageByURL(typeURL.String())
+	emt, err := o.Resolver.FindMessageByURL(typeURL)
 	if !nerr.Merge(err) {
 		return text.Value{}, err
 	}
@@ -393,12 +393,11 @@
 	if !nerr.Merge(err) {
 		return text.Value{}, err
 	}
-	// Expanded Any field value contains only a single field with the embedded
-	// message type as the field name in [] and a text marshaled field value of
-	// the embedded message.
+	// Expanded Any field value contains only a single field with the type_url field value as the
+	// field name in [] and a text marshaled field value of the embedded message.
 	msgFields := [][2]text.Value{
 		{
-			text.ValueOf(string(emt.FullName())),
+			text.ValueOf(typeURL),
 			msg,
 		},
 	}
diff --git a/encoding/textpb/encode_test.go b/encoding/textpb/encode_test.go
index 9871e75..15a5492 100644
--- a/encoding/textpb/encode_test.go
+++ b/encoding/textpb/encode_test.go
@@ -67,6 +67,10 @@
 	knownFields.Set(wire.Number(xd.Field), pval)
 }
 
+func wrapAnyPB(any *anypb.Any) proto.Message {
+	return impl.Export{}.MessageOf(any).Interface()
+}
+
 // dhex decodes a hex-string and returns the bytes and panics if s is invalid.
 func dhex(s string) []byte {
 	b, err := hex.DecodeString(s)
@@ -980,8 +984,10 @@
 		   `,
 		*/
 	}, {
-		desc: "google.protobuf.Any message not expanded",
-		mo:   textpb.MarshalOptions{Resolver: preg.NewTypes()},
+		desc: "Any message not expanded",
+		mo: textpb.MarshalOptions{
+			Resolver: preg.NewTypes(),
+		},
 		input: func() proto.Message {
 			m := &pb2.Nested{
 				OptString: scalar.String("embedded inside Any"),
@@ -994,21 +1000,19 @@
 			if err != nil {
 				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
 			}
-			return impl.Export{}.MessageOf(&anypb.Any{
-				TypeUrl: string(m.ProtoReflect().Type().FullName()),
+			return wrapAnyPB(&anypb.Any{
+				TypeUrl: "pb2.Nested",
 				Value:   b,
-			}).Interface()
+			})
 		}(),
 		want: `type_url: "pb2.Nested"
 value: "\n\x13embedded inside Any\x12\x0b\n\tinception"
 `,
 	}, {
-		desc: "google.protobuf.Any message expanded",
-		mo: func() textpb.MarshalOptions {
-			m := &pb2.Nested{}
-			resolver := preg.NewTypes(m.ProtoReflect().Type())
-			return textpb.MarshalOptions{Resolver: resolver}
-		}(),
+		desc: "Any message expanded",
+		mo: textpb.MarshalOptions{
+			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+		},
 		input: func() proto.Message {
 			m := &pb2.Nested{
 				OptString: scalar.String("embedded inside Any"),
@@ -1021,12 +1025,12 @@
 			if err != nil {
 				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
 			}
-			return impl.Export{}.MessageOf(&anypb.Any{
-				TypeUrl: string(m.ProtoReflect().Type().FullName()),
+			return wrapAnyPB(&anypb.Any{
+				TypeUrl: "foo/pb2.Nested",
 				Value:   b,
-			}).Interface()
+			})
 		}(),
-		want: `[pb2.Nested]: {
+		want: `[foo/pb2.Nested]: {
   opt_string: "embedded inside Any"
   opt_nested: {
     opt_string: "inception"
@@ -1034,12 +1038,10 @@
 }
 `,
 	}, {
-		desc: "google.protobuf.Any message expanded with missing required error",
-		mo: func() textpb.MarshalOptions {
-			m := &pb2.PartialRequired{}
-			resolver := preg.NewTypes(m.ProtoReflect().Type())
-			return textpb.MarshalOptions{Resolver: resolver}
-		}(),
+		desc: "Any message expanded with missing required error",
+		mo: textpb.MarshalOptions{
+			Resolver: preg.NewTypes((&pb2.PartialRequired{}).ProtoReflect().Type()),
+		},
 		input: func() proto.Message {
 			m := &pb2.PartialRequired{
 				OptString: scalar.String("embedded inside Any"),
@@ -1050,10 +1052,10 @@
 			if _, ok := err.(*protoV1.RequiredNotSetError); !ok {
 				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
 			}
-			return impl.Export{}.MessageOf(&anypb.Any{
+			return wrapAnyPB(&anypb.Any{
 				TypeUrl: string(m.ProtoReflect().Type().FullName()),
 				Value:   b,
-			}).Interface()
+			})
 		}(),
 		want: `[pb2.PartialRequired]: {
   opt_string: "embedded inside Any"
@@ -1061,84 +1063,17 @@
 `,
 		wantErr: true,
 	}, {
-		desc: "google.protobuf.Any message with invalid value",
-		mo: func() textpb.MarshalOptions {
-			m := &pb2.Nested{}
-			resolver := preg.NewTypes(m.ProtoReflect().Type())
-			return textpb.MarshalOptions{Resolver: resolver}
-		}(),
-		input: func() proto.Message {
-			m := &pb2.Nested{}
-			return impl.Export{}.MessageOf(&anypb.Any{
-				TypeUrl: string(m.ProtoReflect().Type().FullName()),
-				Value:   dhex("80"),
-			}).Interface()
-		}(),
-		want: `type_url: "pb2.Nested"
+		desc: "Any message with invalid value",
+		mo: textpb.MarshalOptions{
+			Resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+		},
+		input: wrapAnyPB(&anypb.Any{
+			TypeUrl: "foo/pb2.Nested",
+			Value:   dhex("80"),
+		}),
+		want: `type_url: "foo/pb2.Nested"
 value: "\x80"
 `,
-	}, {
-		desc: "google.protobuf.Any field",
-		mo:   textpb.MarshalOptions{Resolver: preg.NewTypes()},
-		input: func() proto.Message {
-			m := &pb2.Nested{
-				OptString: scalar.String("embedded inside Any"),
-				OptNested: &pb2.Nested{
-					OptString: scalar.String("inception"),
-				},
-			}
-			// TODO: Switch to V2 marshal when ready.
-			b, err := protoV1.Marshal(m)
-			if err != nil {
-				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
-			}
-			return &pb2.KnownTypes{
-				OptAny: &anypb.Any{
-					TypeUrl: string(m.ProtoReflect().Type().FullName()),
-					Value:   b,
-				},
-			}
-		}(),
-		want: `opt_any: {
-  type_url: "pb2.Nested"
-  value: "\n\x13embedded inside Any\x12\x0b\n\tinception"
-}
-`,
-	}, {
-		desc: "google.protobuf.Any field expanded using given types registry",
-		mo: func() textpb.MarshalOptions {
-			m := &pb2.Nested{}
-			resolver := preg.NewTypes(m.ProtoReflect().Type())
-			return textpb.MarshalOptions{Resolver: resolver}
-		}(),
-		input: func() proto.Message {
-			m := &pb2.Nested{
-				OptString: scalar.String("embedded inside Any"),
-				OptNested: &pb2.Nested{
-					OptString: scalar.String("inception"),
-				},
-			}
-			// TODO: Switch to V2 marshal when ready.
-			b, err := protoV1.Marshal(m)
-			if err != nil {
-				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
-			}
-			return &pb2.KnownTypes{
-				OptAny: &anypb.Any{
-					TypeUrl: string(m.ProtoReflect().Type().FullName()),
-					Value:   b,
-				},
-			}
-		}(),
-		want: `opt_any: {
-  [pb2.Nested]: {
-    opt_string: "embedded inside Any"
-    opt_nested: {
-      opt_string: "inception"
-    }
-  }
-}
-`,
 	}}
 
 	for _, tt := range tests {
diff --git a/encoding/textpb/other_test.go b/encoding/textpb/other_test.go
index 4092976..4200ac1 100644
--- a/encoding/textpb/other_test.go
+++ b/encoding/textpb/other_test.go
@@ -7,10 +7,13 @@
 	"github.com/golang/protobuf/v2/encoding/textpb"
 	"github.com/golang/protobuf/v2/encoding/textpb/testprotos/pb2"
 	"github.com/golang/protobuf/v2/proto"
+	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
 
 	// The legacy package must be imported prior to use of any legacy messages.
 	// TODO: Remove this when protoV1 registers these hooks for you.
+	"github.com/golang/protobuf/v2/internal/impl"
 	_ "github.com/golang/protobuf/v2/internal/legacy"
+	"github.com/golang/protobuf/v2/internal/scalar"
 
 	anypb "github.com/golang/protobuf/ptypes/any"
 	durpb "github.com/golang/protobuf/ptypes/duration"
@@ -22,8 +25,9 @@
 
 func TestRoundTrip(t *testing.T) {
 	tests := []struct {
-		desc    string
-		message proto.Message
+		desc     string
+		resolver *preg.Types
+		message  proto.Message
 	}{{
 		desc: "well-known type fields set to empty messages",
 		message: &pb2.KnownTypes{
@@ -88,7 +92,7 @@
 			},
 		},
 	}, {
-		desc: "well-known type struct field and different Value types",
+		desc: "Struct field and different Value types",
 		message: &pb2.KnownTypes{
 			OptStruct: &stpb.Struct{
 				Fields: map[string]*stpb.Value{
@@ -146,21 +150,101 @@
 				},
 			},
 		},
+	}, {
+		desc:     "Any field without registered type",
+		resolver: preg.NewTypes(),
+		message: func() proto.Message {
+			m := &pb2.Nested{
+				OptString: scalar.String("embedded inside Any"),
+				OptNested: &pb2.Nested{
+					OptString: scalar.String("inception"),
+				},
+			}
+			// TODO: Switch to V2 marshal when ready.
+			b, err := protoV1.Marshal(m)
+			if err != nil {
+				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
+			}
+			return &pb2.KnownTypes{
+				OptAny: &anypb.Any{
+					TypeUrl: string(m.ProtoReflect().Type().FullName()),
+					Value:   b,
+				},
+			}
+		}(),
+	}, {
+		desc:     "Any field with registered type",
+		resolver: preg.NewTypes((&pb2.Nested{}).ProtoReflect().Type()),
+		message: func() proto.Message {
+			m := &pb2.Nested{
+				OptString: scalar.String("embedded inside Any"),
+				OptNested: &pb2.Nested{
+					OptString: scalar.String("inception"),
+				},
+			}
+			// TODO: Switch to V2 marshal when ready.
+			b, err := protoV1.Marshal(m)
+			if err != nil {
+				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
+			}
+			return &pb2.KnownTypes{
+				OptAny: &anypb.Any{
+					TypeUrl: string(m.ProtoReflect().Type().FullName()),
+					Value:   b,
+				},
+			}
+		}(),
+	}, {
+		desc: "Any field containing Any message",
+		resolver: func() *preg.Types {
+			mt1 := (&pb2.Nested{}).ProtoReflect().Type()
+			mt2 := impl.Export{}.MessageTypeOf(&anypb.Any{})
+			return preg.NewTypes(mt1, mt2)
+		}(),
+		message: func() proto.Message {
+			m1 := &pb2.Nested{
+				OptString: scalar.String("message inside Any of another Any field"),
+			}
+			// TODO: Switch to V2 marshal when ready.
+			b1, err := protoV1.Marshal(m1)
+			if err != nil {
+				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
+			}
+			m2 := &anypb.Any{
+				TypeUrl: "pb2.Nested",
+				Value:   b1,
+			}
+			// TODO: Switch to V2 marshal when ready.
+			b2, err := protoV1.Marshal(m2)
+			if err != nil {
+				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
+			}
+			return &pb2.KnownTypes{
+				OptAny: &anypb.Any{
+					TypeUrl: "google.protobuf.Any",
+					Value:   b2,
+				},
+			}
+		}(),
 	}}
 
 	for _, tt := range tests {
 		tt := tt
 		t.Run(tt.desc, func(t *testing.T) {
 			t.Parallel()
-			b, err := textpb.Marshal(tt.message)
+			mo := textpb.MarshalOptions{Resolver: tt.resolver}
+			umo := textpb.UnmarshalOptions{Resolver: tt.resolver}
+
+			b, err := mo.Marshal(tt.message)
 			if err != nil {
 				t.Errorf("Marshal() returned error: %v\n\n", err)
 			}
 			gotMessage := tt.message.ProtoReflect().Type().New().Interface()
-			err = textpb.Unmarshal(gotMessage, b)
+			err = umo.Unmarshal(gotMessage, b)
 			if err != nil {
 				t.Errorf("Unmarshal() returned error: %v\n\n", err)
 			}
+
 			if !protoV1.Equal(gotMessage.(protoV1.Message), tt.message.(protoV1.Message)) {
 				t.Errorf("Unmarshal()\n<got>\n%v\n<want>\n%v\n", gotMessage, tt.message)
 			}