From 62d7fe569717b6da39401a94b22adf73466e1f2d Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 3 Jul 2017 07:40:45 +0100 Subject: Make Any easier to work with in C# - Add a TryUnpack method which doesn't throw if the type is wrong - Make GetTypeName public for easier determination of the message type Fixes #3294. --- .../Google.Protobuf.Test/WellKnownTypes/AnyTest.cs | 28 ++++++++++++++ .../Google.Protobuf/WellKnownTypes/AnyPartial.cs | 43 ++++++++++++++++++---- 2 files changed, 64 insertions(+), 7 deletions(-) (limited to 'csharp') diff --git a/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs b/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs index 4aecc998..a3edd595 100644 --- a/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs +++ b/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs @@ -90,6 +90,24 @@ namespace Google.Protobuf.WellKnownTypes Assert.AreEqual(message, unpacked); } + [Test] + public void TryUnpack_WrongType() + { + var message = SampleMessages.CreateFullTestAllTypes(); + var any = Any.Pack(message); + Assert.False(any.TryUnpack(out TestOneof unpacked)); + Assert.Null(unpacked); + } + + [Test] + public void TryUnpack_RightType() + { + var message = SampleMessages.CreateFullTestAllTypes(); + var any = Any.Pack(message); + Assert.IsTrue(any.TryUnpack(out TestAllTypes unpacked)); + Assert.AreEqual(message, unpacked); + } + [Test] public void ToString_WithValues() { @@ -99,6 +117,16 @@ namespace Google.Protobuf.WellKnownTypes Assert.That(text, Does.Contain("\"@value\": \"" + message.ToByteString().ToBase64() + "\"")); } + [Test] + [TestCase("proto://foo.bar", "foo.bar")] + [TestCase("/foo/bar/baz", "baz")] + [TestCase("foobar", "")] + public void GetTypeName(string typeUrl, string expectedTypeName) + { + var any = new Any { TypeUrl = typeUrl }; + Assert.AreEqual(expectedTypeName, Any.GetTypeName(typeUrl)); + } + [Test] public void ToString_Empty() { diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs index f4fac738..fca689dc 100644 --- a/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs +++ b/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs @@ -44,17 +44,25 @@ namespace Google.Protobuf.WellKnownTypes prefix.EndsWith("/") ? prefix + descriptor.FullName : prefix + "/" + descriptor.FullName; /// - /// Retrieves the type name for a type URL. This is always just the last part of the URL, - /// after the trailing slash. No validation of anything before the trailing slash is performed. - /// If the type URL does not include a slash, an empty string is returned rather than an exception - /// being thrown; this won't match any types, and the calling code is probably in a better position - /// to give a meaningful error. - /// There is no handling of fragments or queries at the moment. + /// Retrieves the type name for a type URL, matching the + /// of the packed message type. /// + /// + /// + /// This is always just the last part of the URL, after the final slash. No validation of + /// anything before the trailing slash is performed. If the type URL does not include a slash, + /// an empty string is returned rather than an exception being thrown; this won't match any types, + /// and the calling code is probably in a better position to give a meaningful error. + /// + /// + /// There is no handling of fragments or queries at the moment. + /// + /// /// The URL to extract the type name from /// The type name - internal static string GetTypeName(string typeUrl) + public static string GetTypeName(string typeUrl) { + ProtoPreconditions.CheckNotNull(typeUrl, nameof(typeUrl)); int lastSlash = typeUrl.LastIndexOf('/'); return lastSlash == -1 ? "" : typeUrl.Substring(lastSlash + 1); } @@ -80,6 +88,27 @@ namespace Google.Protobuf.WellKnownTypes return target; } + /// + /// Attempts to unpack the content of this Any message into the target message type, + /// if it matches the type URL within this Any message. + /// + /// The type of message to attempt to unpack the content into. + /// true if the message was successfully unpacked; false if the type name didn't match + public bool TryUnpack(out T result) where T : IMessage, new() + { + // Note: deliberately avoid writing anything to result until the end, in case it's being + // monitored by other threads. (That would be a bug in the calling code, but let's not make it worse.) + T target = new T(); + if (GetTypeName(TypeUrl) != target.Descriptor.FullName) + { + result = default(T); // Can't use null as there's no class constraint, but this always *will* be null in real usage. + return false; + } + target.MergeFrom(Value); + result = target; + return true; + } + /// /// Packs the specified message into an Any message using a type URL prefix of "type.googleapis.com". /// -- cgit v1.2.3