diff options
Diffstat (limited to 'csharp')
32 files changed, 496 insertions, 96 deletions
diff --git a/csharp/Google.Protobuf.Tools.nuspec b/csharp/Google.Protobuf.Tools.nuspec index 8a0d61e1..05779d44 100644 --- a/csharp/Google.Protobuf.Tools.nuspec +++ b/csharp/Google.Protobuf.Tools.nuspec @@ -8,8 +8,8 @@ <version>3.6.1</version> <authors>Google Inc.</authors> <owners>protobuf-packages</owners> - <licenseUrl>https://github.com/google/protobuf/blob/master/LICENSE</licenseUrl> - <projectUrl>https://github.com/google/protobuf</projectUrl> + <licenseUrl>https://github.com/protocolbuffers/protobuf/blob/master/LICENSE</licenseUrl> + <projectUrl>https://github.com/protocolbuffers/protobuf</projectUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> <releaseNotes>Tools for Protocol Buffers</releaseNotes> <copyright>Copyright 2015, Google Inc.</copyright> @@ -33,5 +33,7 @@ <file src="..\src\google\protobuf\timestamp.proto" target="tools\google\protobuf" /> <file src="..\src\google\protobuf\type.proto" target="tools\google\protobuf" /> <file src="..\src\google\protobuf\wrappers.proto" target="tools\google\protobuf" /> + <file src="Google.Protobuf.Tools.targets" target="buildCrossTargeting" /> + <file src="Google.Protobuf.Tools.targets" target="build" /> </files> </package> diff --git a/csharp/Google.Protobuf.Tools.targets b/csharp/Google.Protobuf.Tools.targets new file mode 100644 index 00000000..682e11b0 --- /dev/null +++ b/csharp/Google.Protobuf.Tools.targets @@ -0,0 +1,11 @@ +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <protoc_tools>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)/../tools/'))</protoc_tools>
+ <protoc_linux64>$([System.IO.Path]::GetFullPath('$(protoc_tools)/linux_x64/protoc'))</protoc_linux64>
+ <protoc_linux86>$([System.IO.Path]::GetFullPath('$(protoc_tools)/linux_x86/protoc'))</protoc_linux86>
+ <protoc_macosx64>$([System.IO.Path]::GetFullPath('$(protoc_tools)/macosx_x64/protoc'))</protoc_macosx64>
+ <protoc_macosx86>$([System.IO.Path]::GetFullPath('$(protoc_tools)/macosx_x86/protoc'))</protoc_macosx86>
+ <protoc_windows64>$([System.IO.Path]::GetFullPath('$(protoc_tools)/windows_x64/protoc.exe'))</protoc_windows64>
+ <protoc_windows86>$([System.IO.Path]::GetFullPath('$(protoc_tools)/windows_x86/protoc.exe'))</protoc_windows86>
+ </PropertyGroup>
+</Project>
diff --git a/csharp/README.md b/csharp/README.md index 9d1225f1..aafef16a 100644 --- a/csharp/README.md +++ b/csharp/README.md @@ -77,7 +77,7 @@ History of C# protobufs This subtree was originally imported from https://github.com/jskeet/protobuf-csharp-port and represents the latest development version of C# protobufs, that will now be developed and maintained by Google. All the development will be done in open, under this repository -(https://github.com/google/protobuf). +(https://github.com/protocolbuffers/protobuf). The previous project differs from this project in a number of ways: diff --git a/csharp/build_packages.bat b/csharp/build_packages.bat index d7205659..8157bbab 100644 --- a/csharp/build_packages.bat +++ b/csharp/build_packages.bat @@ -1,7 +1,7 @@ @rem Builds Google.Protobuf NuGet packages dotnet restore src/Google.Protobuf.sln -dotnet pack -c Release src/Google.Protobuf.sln || goto :error +dotnet pack -c Release src/Google.Protobuf.sln /p:SourceLinkCreate=true || goto :error goto :EOF diff --git a/csharp/compatibility_tests/v3.0.0/protos/csharp/protos/unittest_issues.proto b/csharp/compatibility_tests/v3.0.0/protos/csharp/protos/unittest_issues.proto index 6c9f7634..7bec1f80 100644 --- a/csharp/compatibility_tests/v3.0.0/protos/csharp/protos/unittest_issues.proto +++ b/csharp/compatibility_tests/v3.0.0/protos/csharp/protos/unittest_issues.proto @@ -19,7 +19,7 @@ message Issue307 { } // Old issue 13: http://code.google.com/p/protobuf-csharp-port/issues/detail?id=13 -// New issue 309: https://github.com/google/protobuf/issues/309 +// New issue 309: https://github.com/protocolbuffers/protobuf/issues/309 // message A { // optional int32 _A = 1; @@ -35,7 +35,7 @@ message Issue307 { // Similar issue with numeric names // Java code failed too, so probably best for this to be a restriction. -// See https://github.com/google/protobuf/issues/308 +// See https://github.com/protocolbuffers/protobuf/issues/308 // message NumberField { // optional int32 _01 = 1; // } diff --git a/csharp/protos/unittest_issues.proto b/csharp/protos/unittest_issues.proto index 0d8793e1..1619f13d 100644 --- a/csharp/protos/unittest_issues.proto +++ b/csharp/protos/unittest_issues.proto @@ -18,7 +18,7 @@ message Issue307 { } // Old issue 13: http://code.google.com/p/protobuf-csharp-port/issues/detail?id=13 -// New issue 309: https://github.com/google/protobuf/issues/309 +// New issue 309: https://github.com/protocolbuffers/protobuf/issues/309 // message A { // optional int32 _A = 1; @@ -34,7 +34,7 @@ message Issue307 { // Similar issue with numeric names // Java code failed too, so probably best for this to be a restriction. -// See https://github.com/google/protobuf/issues/308 +// See https://github.com/protocolbuffers/protobuf/issues/308 // message NumberField { // optional int32 _01 = 1; // } diff --git a/csharp/src/Google.Protobuf.Conformance/Conformance.cs b/csharp/src/Google.Protobuf.Conformance/Conformance.cs index f6118ea2..46793ce1 100644 --- a/csharp/src/Google.Protobuf.Conformance/Conformance.cs +++ b/csharp/src/Google.Protobuf.Conformance/Conformance.cs @@ -24,21 +24,25 @@ namespace Conformance { static ConformanceReflection() { byte[] descriptorData = global::System.Convert.FromBase64String( string.Concat( - "ChFjb25mb3JtYW5jZS5wcm90bxILY29uZm9ybWFuY2UiowEKEkNvbmZvcm1h", + "ChFjb25mb3JtYW5jZS5wcm90bxILY29uZm9ybWFuY2Ui1QEKEkNvbmZvcm1h", "bmNlUmVxdWVzdBIaChBwcm90b2J1Zl9wYXlsb2FkGAEgASgMSAASFgoManNv", "bl9wYXlsb2FkGAIgASgJSAASOAoXcmVxdWVzdGVkX291dHB1dF9mb3JtYXQY", "AyABKA4yFy5jb25mb3JtYW5jZS5XaXJlRm9ybWF0EhQKDG1lc3NhZ2VfdHlw", - "ZRgEIAEoCUIJCgdwYXlsb2FkIrEBChNDb25mb3JtYW5jZVJlc3BvbnNlEhUK", - "C3BhcnNlX2Vycm9yGAEgASgJSAASGQoPc2VyaWFsaXplX2Vycm9yGAYgASgJ", - "SAASFwoNcnVudGltZV9lcnJvchgCIAEoCUgAEhoKEHByb3RvYnVmX3BheWxv", - "YWQYAyABKAxIABIWCgxqc29uX3BheWxvYWQYBCABKAlIABIRCgdza2lwcGVk", - "GAUgASgJSABCCAoGcmVzdWx0KjUKCldpcmVGb3JtYXQSDwoLVU5TUEVDSUZJ", - "RUQQABIMCghQUk9UT0JVRhABEggKBEpTT04QAkIhCh9jb20uZ29vZ2xlLnBy", - "b3RvYnVmLmNvbmZvcm1hbmNlYgZwcm90bzM=")); + "ZRgEIAEoCRIwCg10ZXN0X2NhdGVnb3J5GAUgASgOMhkuY29uZm9ybWFuY2Uu", + "VGVzdENhdGVnb3J5QgkKB3BheWxvYWQisQEKE0NvbmZvcm1hbmNlUmVzcG9u", + "c2USFQoLcGFyc2VfZXJyb3IYASABKAlIABIZCg9zZXJpYWxpemVfZXJyb3IY", + "BiABKAlIABIXCg1ydW50aW1lX2Vycm9yGAIgASgJSAASGgoQcHJvdG9idWZf", + "cGF5bG9hZBgDIAEoDEgAEhYKDGpzb25fcGF5bG9hZBgEIAEoCUgAEhEKB3Nr", + "aXBwZWQYBSABKAlIAEIICgZyZXN1bHQqNQoKV2lyZUZvcm1hdBIPCgtVTlNQ", + "RUNJRklFRBAAEgwKCFBST1RPQlVGEAESCAoESlNPThACKmoKDFRlc3RDYXRl", + "Z29yeRIUChBVTlNQRUNJRklFRF9URVNUEAASDwoLQklOQVJZX1RFU1QQARIN", + "CglKU09OX1RFU1QQAhIkCiBKU09OX0lHTk9SRV9VTktOT1dOX1BBUlNJTkdf", + "VEVTVBADQiEKH2NvbS5nb29nbGUucHJvdG9idWYuY29uZm9ybWFuY2ViBnBy", + "b3RvMw==")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { }, - new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Conformance.WireFormat), }, new pbr::GeneratedClrTypeInfo[] { - new pbr::GeneratedClrTypeInfo(typeof(global::Conformance.ConformanceRequest), global::Conformance.ConformanceRequest.Parser, new[]{ "ProtobufPayload", "JsonPayload", "RequestedOutputFormat", "MessageType" }, new[]{ "Payload" }, null, null), + new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Conformance.WireFormat), typeof(global::Conformance.TestCategory), }, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Conformance.ConformanceRequest), global::Conformance.ConformanceRequest.Parser, new[]{ "ProtobufPayload", "JsonPayload", "RequestedOutputFormat", "MessageType", "TestCategory" }, new[]{ "Payload" }, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Conformance.ConformanceResponse), global::Conformance.ConformanceResponse.Parser, new[]{ "ParseError", "SerializeError", "RuntimeError", "ProtobufPayload", "JsonPayload", "Skipped" }, new[]{ "Result" }, null, null) })); } @@ -52,6 +56,26 @@ namespace Conformance { [pbr::OriginalName("JSON")] Json = 2, } + public enum TestCategory { + [pbr::OriginalName("UNSPECIFIED_TEST")] UnspecifiedTest = 0, + /// <summary> + /// Test binary wire format. + /// </summary> + [pbr::OriginalName("BINARY_TEST")] BinaryTest = 1, + /// <summary> + /// Test json wire format. + /// </summary> + [pbr::OriginalName("JSON_TEST")] JsonTest = 2, + /// <summary> + /// Similar to JSON_TEST. However, during parsing json, testee should ignore + /// unknown fields. This feature is optional. Each implementation can descide + /// whether to support it. See + /// https://developers.google.com/protocol-buffers/docs/proto3#json_options + /// for more detail. + /// </summary> + [pbr::OriginalName("JSON_IGNORE_UNKNOWN_PARSING_TEST")] JsonIgnoreUnknownParsingTest = 3, + } + #endregion #region Messages @@ -89,6 +113,7 @@ namespace Conformance { public ConformanceRequest(ConformanceRequest other) : this() { requestedOutputFormat_ = other.requestedOutputFormat_; messageType_ = other.messageType_; + testCategory_ = other.testCategory_; switch (other.PayloadCase) { case PayloadOneofCase.ProtobufPayload: ProtobufPayload = other.ProtobufPayload; @@ -158,6 +183,22 @@ namespace Conformance { } } + /// <summary>Field number for the "test_category" field.</summary> + public const int TestCategoryFieldNumber = 5; + private global::Conformance.TestCategory testCategory_ = 0; + /// <summary> + /// Each test is given a specific test category. Some category may need + /// spedific support in testee programs. Refer to the defintion of TestCategory + /// for more information. + /// </summary> + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Conformance.TestCategory TestCategory { + get { return testCategory_; } + set { + testCategory_ = value; + } + } + private object payload_; /// <summary>Enum of possible cases for the "payload" oneof.</summary> public enum PayloadOneofCase { @@ -194,6 +235,7 @@ namespace Conformance { if (JsonPayload != other.JsonPayload) return false; if (RequestedOutputFormat != other.RequestedOutputFormat) return false; if (MessageType != other.MessageType) return false; + if (TestCategory != other.TestCategory) return false; if (PayloadCase != other.PayloadCase) return false; return Equals(_unknownFields, other._unknownFields); } @@ -205,6 +247,7 @@ namespace Conformance { if (payloadCase_ == PayloadOneofCase.JsonPayload) hash ^= JsonPayload.GetHashCode(); if (RequestedOutputFormat != 0) hash ^= RequestedOutputFormat.GetHashCode(); if (MessageType.Length != 0) hash ^= MessageType.GetHashCode(); + if (TestCategory != 0) hash ^= TestCategory.GetHashCode(); hash ^= (int) payloadCase_; if (_unknownFields != null) { hash ^= _unknownFields.GetHashCode(); @@ -235,6 +278,10 @@ namespace Conformance { output.WriteRawTag(34); output.WriteString(MessageType); } + if (TestCategory != 0) { + output.WriteRawTag(40); + output.WriteEnum((int) TestCategory); + } if (_unknownFields != null) { _unknownFields.WriteTo(output); } @@ -255,6 +302,9 @@ namespace Conformance { if (MessageType.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeStringSize(MessageType); } + if (TestCategory != 0) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) TestCategory); + } if (_unknownFields != null) { size += _unknownFields.CalculateSize(); } @@ -272,6 +322,9 @@ namespace Conformance { if (other.MessageType.Length != 0) { MessageType = other.MessageType; } + if (other.TestCategory != 0) { + TestCategory = other.TestCategory; + } switch (other.PayloadCase) { case PayloadOneofCase.ProtobufPayload: ProtobufPayload = other.ProtobufPayload; @@ -308,6 +361,10 @@ namespace Conformance { MessageType = input.ReadString(); break; } + case 40: { + testCategory_ = (global::Conformance.TestCategory) input.ReadEnum(); + break; + } } } } diff --git a/csharp/src/Google.Protobuf.Conformance/Program.cs b/csharp/src/Google.Protobuf.Conformance/Program.cs index 96dc354e..1eac00be 100644 --- a/csharp/src/Google.Protobuf.Conformance/Program.cs +++ b/csharp/src/Google.Protobuf.Conformance/Program.cs @@ -87,6 +87,9 @@ namespace Google.Protobuf.Conformance switch (request.PayloadCase) { case ConformanceRequest.PayloadOneofCase.JsonPayload: + if (request.TestCategory == global::Conformance.TestCategory.JsonIgnoreUnknownParsingTest) { + return new ConformanceResponse { Skipped = "CSharp doesn't support skipping unknown fields in json parsing." }; + } var parser = new JsonParser(new JsonParser.Settings(20, typeRegistry)); message = parser.Parse<ProtobufTestMessages.Proto3.TestAllTypesProto3>(request.JsonPayload); break; diff --git a/csharp/src/Google.Protobuf.Test/ByteStringTest.cs b/csharp/src/Google.Protobuf.Test/ByteStringTest.cs index afdd491f..84e6341e 100755..100644 --- a/csharp/src/Google.Protobuf.Test/ByteStringTest.cs +++ b/csharp/src/Google.Protobuf.Test/ByteStringTest.cs @@ -227,7 +227,7 @@ namespace Google.Protobuf {
// We used to have an awful hash algorithm where only the last four
// bytes were relevant. This is a regression test for
- // https://github.com/google/protobuf/issues/2511
+ // https://github.com/protocolbuffers/protobuf/issues/2511
ByteString b1 = ByteString.CopyFrom(100, 1, 2, 3, 4);
ByteString b2 = ByteString.CopyFrom(200, 1, 2, 3, 4);
diff --git a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs index 8795fa65..8795fa65 100755..100644 --- a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs +++ b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs diff --git a/csharp/src/Google.Protobuf.Test/Compatibility/StreamExtensionsTest.cs b/csharp/src/Google.Protobuf.Test/Compatibility/StreamExtensionsTest.cs index 48c0725f..48c0725f 100755..100644 --- a/csharp/src/Google.Protobuf.Test/Compatibility/StreamExtensionsTest.cs +++ b/csharp/src/Google.Protobuf.Test/Compatibility/StreamExtensionsTest.cs diff --git a/csharp/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs b/csharp/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs index abbe3c95..abbe3c95 100755..100644 --- a/csharp/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs +++ b/csharp/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs diff --git a/csharp/src/Google.Protobuf.Test/FieldCodecTest.cs b/csharp/src/Google.Protobuf.Test/FieldCodecTest.cs index 77641163..77641163 100755..100644 --- a/csharp/src/Google.Protobuf.Test/FieldCodecTest.cs +++ b/csharp/src/Google.Protobuf.Test/FieldCodecTest.cs diff --git a/csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs b/csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs index 29a376e0..9abee951 100644 --- a/csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs +++ b/csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs @@ -30,10 +30,11 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion -using System.Linq; using Google.Protobuf.TestProtos; using NUnit.Framework; -using UnitTest.Issues.TestProtos; +using System; +using System.Collections.Generic; +using System.Linq; namespace Google.Protobuf.Reflection { @@ -44,9 +45,32 @@ namespace Google.Protobuf.Reflection public class DescriptorsTest { [Test] - public void FileDescriptor() + public void FileDescriptor_GeneratedCode() + { + TestFileDescriptor( + UnittestProto3Reflection.Descriptor, + UnittestImportProto3Reflection.Descriptor, + UnittestImportPublicProto3Reflection.Descriptor); + } + + [Test] + public void FileDescriptor_BuildFromByteStrings() + { + // The descriptors have to be supplied in an order such that all the + // dependencies come before the descriptors depending on them. + var descriptorData = new List<ByteString> + { + UnittestImportPublicProto3Reflection.Descriptor.Proto.ToByteString(), + UnittestImportProto3Reflection.Descriptor.Proto.ToByteString(), + UnittestProto3Reflection.Descriptor.Proto.ToByteString() + }; + var converted = FileDescriptor.BuildFromByteStrings(descriptorData); + Assert.AreEqual(3, converted.Count); + TestFileDescriptor(converted[2], converted[1], converted[0]); + } + + private void TestFileDescriptor(FileDescriptor file, FileDescriptor importedFile, FileDescriptor importedPublicFile) { - FileDescriptor file = UnittestProto3Reflection.Descriptor; Assert.AreEqual("unittest_proto3.proto", file.Name); Assert.AreEqual("protobuf_unittest3", file.Package); @@ -56,17 +80,12 @@ namespace Google.Protobuf.Reflection // unittest_proto3.proto doesn't have any public imports, but unittest_import_proto3.proto does. Assert.AreEqual(0, file.PublicDependencies.Count); - Assert.AreEqual(1, UnittestImportProto3Reflection.Descriptor.PublicDependencies.Count); - Assert.AreEqual(UnittestImportPublicProto3Reflection.Descriptor, UnittestImportProto3Reflection.Descriptor.PublicDependencies[0]); + Assert.AreEqual(1, importedFile.PublicDependencies.Count); + Assert.AreEqual(importedPublicFile, importedFile.PublicDependencies[0]); Assert.AreEqual(1, file.Dependencies.Count); - Assert.AreEqual(UnittestImportProto3Reflection.Descriptor, file.Dependencies[0]); + Assert.AreEqual(importedFile, file.Dependencies[0]); - MessageDescriptor messageType = TestAllTypes.Descriptor; - Assert.AreSame(typeof(TestAllTypes), messageType.ClrType); - Assert.AreSame(TestAllTypes.Parser, messageType.Parser); - Assert.AreEqual(messageType, file.MessageTypes[0]); - Assert.AreEqual(messageType, file.FindTypeByName<MessageDescriptor>("TestAllTypes")); Assert.Null(file.FindTypeByName<MessageDescriptor>("NoSuchType")); Assert.Null(file.FindTypeByName<MessageDescriptor>("protobuf_unittest3.TestAllTypes")); for (int i = 0; i < file.MessageTypes.Count; i++) @@ -77,8 +96,8 @@ namespace Google.Protobuf.Reflection Assert.AreEqual(file.EnumTypes[0], file.FindTypeByName<EnumDescriptor>("ForeignEnum")); Assert.Null(file.FindTypeByName<EnumDescriptor>("NoSuchType")); Assert.Null(file.FindTypeByName<EnumDescriptor>("protobuf_unittest3.ForeignEnum")); - Assert.AreEqual(1, UnittestImportProto3Reflection.Descriptor.EnumTypes.Count); - Assert.AreEqual("ImportEnum", UnittestImportProto3Reflection.Descriptor.EnumTypes[0].Name); + Assert.AreEqual(1, importedFile.EnumTypes.Count); + Assert.AreEqual("ImportEnum", importedFile.EnumTypes[0].Name); for (int i = 0; i < file.EnumTypes.Count; i++) { Assert.AreEqual(i, file.EnumTypes[i].Index); @@ -98,6 +117,56 @@ namespace Google.Protobuf.Reflection } [Test] + public void FileDescriptor_BuildFromByteStrings_MissingDependency() + { + var descriptorData = new List<ByteString> + { + UnittestImportProto3Reflection.Descriptor.Proto.ToByteString(), + UnittestProto3Reflection.Descriptor.Proto.ToByteString(), + }; + // This will fail, because we're missing UnittestImportPublicProto3Reflection + Assert.Throws<ArgumentException>(() => FileDescriptor.BuildFromByteStrings(descriptorData)); + } + + [Test] + public void FileDescriptor_BuildFromByteStrings_DuplicateNames() + { + var descriptorData = new List<ByteString> + { + UnittestImportPublicProto3Reflection.Descriptor.Proto.ToByteString(), + UnittestImportPublicProto3Reflection.Descriptor.Proto.ToByteString(), + }; + // This will fail due to the same name being used twice + Assert.Throws<ArgumentException>(() => FileDescriptor.BuildFromByteStrings(descriptorData)); + } + + [Test] + public void FileDescriptor_BuildFromByteStrings_IncorrectOrder() + { + var descriptorData = new List<ByteString> + { + UnittestProto3Reflection.Descriptor.Proto.ToByteString(), + UnittestImportPublicProto3Reflection.Descriptor.Proto.ToByteString(), + UnittestImportProto3Reflection.Descriptor.Proto.ToByteString() + }; + // This will fail, because the dependencies should come first + Assert.Throws<ArgumentException>(() => FileDescriptor.BuildFromByteStrings(descriptorData)); + + } + + [Test] + public void MessageDescriptorFromGeneratedCodeFileDescriptor() + { + var file = UnittestProto3Reflection.Descriptor; + + MessageDescriptor messageType = TestAllTypes.Descriptor; + Assert.AreSame(typeof(TestAllTypes), messageType.ClrType); + Assert.AreSame(TestAllTypes.Parser, messageType.Parser); + Assert.AreEqual(messageType, file.MessageTypes[0]); + Assert.AreEqual(messageType, file.FindTypeByName<MessageDescriptor>("TestAllTypes")); + } + + [Test] public void MessageDescriptor() { MessageDescriptor messageType = TestAllTypes.Descriptor; @@ -163,7 +232,7 @@ namespace Google.Protobuf.Reflection Assert.AreEqual("single_nested_enum", enumField.Name); Assert.AreEqual(FieldType.Enum, enumField.FieldType); - // Assert.AreEqual(TestAllTypes.Types.NestedEnum.DescriptorProtoFile, enumField.EnumType); + Assert.AreEqual(messageType.EnumTypes[0], enumField.EnumType); Assert.AreEqual("single_foreign_message", messageField.Name); Assert.AreEqual(FieldType.Message, messageField.FieldType); diff --git a/csharp/src/Google.Protobuf.Test/WellKnownTypes/TimestampTest.cs b/csharp/src/Google.Protobuf.Test/WellKnownTypes/TimestampTest.cs index 9ecd24c6..b8c07ef5 100644 --- a/csharp/src/Google.Protobuf.Test/WellKnownTypes/TimestampTest.cs +++ b/csharp/src/Google.Protobuf.Test/WellKnownTypes/TimestampTest.cs @@ -111,5 +111,106 @@ namespace Google.Protobuf.WellKnownTypes var duration = new Timestamp { Seconds = 1, Nanos = -1 }; Assert.AreEqual("{ \"@warning\": \"Invalid Timestamp\", \"seconds\": \"1\", \"nanos\": -1 }", duration.ToString()); } + + [Test] + public void Comparability() + { + Timestamp + a = null, + b = new Timestamp { Seconds = 1, Nanos = 1 }, + c = new Timestamp { Seconds = 1, Nanos = 10 }, + d = new Timestamp { Seconds = 10, Nanos = 1 }, + e = new Timestamp { Seconds = 10, Nanos = 10 }; + + Assert.IsTrue(b.CompareTo(a) > 0); // null is always first (according to default behavior of Array.Sort) + Assert.IsTrue(b.CompareTo(b) == 0); + Assert.IsTrue(b.CompareTo(b.Clone()) == 0); + Assert.IsTrue(b.CompareTo(c) < 0); + Assert.IsTrue(b.CompareTo(d) < 0); + Assert.IsTrue(b.CompareTo(e) < 0); + + Assert.IsTrue(c.CompareTo(a) > 0); + Assert.IsTrue(c.CompareTo(b) > 0); + Assert.IsTrue(c.CompareTo(c) == 0); + Assert.IsTrue(c.CompareTo(c.Clone()) == 0); + Assert.IsTrue(c.CompareTo(d) < 0); + Assert.IsTrue(c.CompareTo(e) < 0); + + Assert.IsTrue(d.CompareTo(a) > 0); + Assert.IsTrue(d.CompareTo(b) > 0); + Assert.IsTrue(d.CompareTo(c) > 0); + Assert.IsTrue(d.CompareTo(d) == 0); + Assert.IsTrue(d.CompareTo(d.Clone()) == 0); + Assert.IsTrue(d.CompareTo(e) < 0); + + Assert.IsTrue(e.CompareTo(a) > 0); + Assert.IsTrue(e.CompareTo(b) > 0); + Assert.IsTrue(e.CompareTo(c) > 0); + Assert.IsTrue(e.CompareTo(d) > 0); + Assert.IsTrue(e.CompareTo(e) == 0); + Assert.IsTrue(e.CompareTo(e.Clone()) == 0); + } + + + [Test] + public void ComparabilityOperators() + { + Timestamp + a = null, + b = new Timestamp { Seconds = 1, Nanos = 1 }, + c = new Timestamp { Seconds = 1, Nanos = 10 }, + d = new Timestamp { Seconds = 10, Nanos = 1 }, + e = new Timestamp { Seconds = 10, Nanos = 10 }; + +#pragma warning disable CS1718 // Comparison made to same variable + Assert.IsTrue(b > a); + Assert.IsTrue(b == b); + Assert.IsTrue(b == b.Clone()); + Assert.IsTrue(b < c); + Assert.IsTrue(b < d); + Assert.IsTrue(b < e); + + Assert.IsTrue(c > a); + Assert.IsTrue(c > b); + Assert.IsTrue(c == c); + Assert.IsTrue(c == c.Clone()); + Assert.IsTrue(c < d); + Assert.IsTrue(c < e); + + Assert.IsTrue(d > a); + Assert.IsTrue(d > b); + Assert.IsTrue(d > c); + Assert.IsTrue(d == d); + Assert.IsTrue(d == d.Clone()); + Assert.IsTrue(d < e); + + Assert.IsTrue(e > a); + Assert.IsTrue(e > b); + Assert.IsTrue(e > c); + Assert.IsTrue(e > d); + Assert.IsTrue(e == e); + Assert.IsTrue(e == e.Clone()); + + Assert.IsTrue(b >= a); + Assert.IsTrue(b <= c); + Assert.IsTrue(b <= d); + Assert.IsTrue(b <= e); + + Assert.IsTrue(c >= a); + Assert.IsTrue(c >= b); + Assert.IsTrue(c <= d); + Assert.IsTrue(c <= e); + + Assert.IsTrue(d >= a); + Assert.IsTrue(d >= b); + Assert.IsTrue(d >= c); + Assert.IsTrue(d <= e); + + Assert.IsTrue(e >= a); + Assert.IsTrue(e >= b); + Assert.IsTrue(e >= c); + Assert.IsTrue(e >= d); +#pragma warning restore CS1718 // Comparison made to same variable + } } } diff --git a/csharp/src/Google.Protobuf/ByteString.cs b/csharp/src/Google.Protobuf/ByteString.cs index 4abdb718..4abdb718 100755..100644 --- a/csharp/src/Google.Protobuf/ByteString.cs +++ b/csharp/src/Google.Protobuf/ByteString.cs diff --git a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs index c18b63e2..c18b63e2 100755..100644 --- a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs +++ b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs diff --git a/csharp/src/Google.Protobuf/Compatibility/PropertyInfoExtensions.cs b/csharp/src/Google.Protobuf/Compatibility/PropertyInfoExtensions.cs index 95a02c72..95a02c72 100755..100644 --- a/csharp/src/Google.Protobuf/Compatibility/PropertyInfoExtensions.cs +++ b/csharp/src/Google.Protobuf/Compatibility/PropertyInfoExtensions.cs diff --git a/csharp/src/Google.Protobuf/Compatibility/StreamExtensions.cs b/csharp/src/Google.Protobuf/Compatibility/StreamExtensions.cs index bf4bf220..bf4bf220 100755..100644 --- a/csharp/src/Google.Protobuf/Compatibility/StreamExtensions.cs +++ b/csharp/src/Google.Protobuf/Compatibility/StreamExtensions.cs diff --git a/csharp/src/Google.Protobuf/Compatibility/TypeExtensions.cs b/csharp/src/Google.Protobuf/Compatibility/TypeExtensions.cs index 2f237138..2f237138 100755..100644 --- a/csharp/src/Google.Protobuf/Compatibility/TypeExtensions.cs +++ b/csharp/src/Google.Protobuf/Compatibility/TypeExtensions.cs diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj index 11bc03d1..8ea3818a 100644 --- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj +++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj @@ -13,12 +13,10 @@ <PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign> <PackageTags>Protocol;Buffers;Binary;Serialization;Format;Google;proto;proto3</PackageTags> <PackageReleaseNotes>C# proto3 support</PackageReleaseNotes> - <PackageProjectUrl>https://github.com/google/protobuf</PackageProjectUrl> - <PackageLicenseUrl>https://github.com/google/protobuf/blob/master/LICENSE</PackageLicenseUrl> + <PackageProjectUrl>https://github.com/protocolbuffers/protobuf</PackageProjectUrl> + <PackageLicenseUrl>https://github.com/protocolbuffers/protobuf/blob/master/LICENSE</PackageLicenseUrl> <RepositoryType>git</RepositoryType> - <RepositoryUrl>https://github.com/google/protobuf.git</RepositoryUrl> - <IncludeSymbols>true</IncludeSymbols> - <IncludeSource>true</IncludeSource> + <RepositoryUrl>https://github.com/protocolbuffers/protobuf.git</RepositoryUrl> </PropertyGroup> <!-- @@ -30,4 +28,8 @@ <TargetFrameworks>netstandard1.0</TargetFrameworks> </PropertyGroup> + <ItemGroup> + <PackageReference Include="SourceLink.Create.CommandLine" Version="2.7.6" PrivateAssets="All" /> + </ItemGroup> + </Project> diff --git a/csharp/src/Google.Protobuf/JsonFormatter.cs b/csharp/src/Google.Protobuf/JsonFormatter.cs index 4ae10d8b..4ae10d8b 100755..100644 --- a/csharp/src/Google.Protobuf/JsonFormatter.cs +++ b/csharp/src/Google.Protobuf/JsonFormatter.cs diff --git a/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs b/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs index 99ca4bf3..9c2160fe 100644 --- a/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs +++ b/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs @@ -53,13 +53,13 @@ namespace Google.Protobuf.Reflection private readonly HashSet<FileDescriptor> dependencies; - internal DescriptorPool(FileDescriptor[] dependencyFiles) + internal DescriptorPool(IEnumerable<FileDescriptor> dependencyFiles) { dependencies = new HashSet<FileDescriptor>(); - for (int i = 0; i < dependencyFiles.Length; i++) + foreach (var dependencyFile in dependencyFiles) { - dependencies.Add(dependencyFiles[i]); - ImportPublicDependencies(dependencyFiles[i]); + dependencies.Add(dependencyFile); + ImportPublicDependencies(dependencyFile); } foreach (FileDescriptor dependency in dependencyFiles) diff --git a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs index 2a3d5c7a..152467d8 100644 --- a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs @@ -116,13 +116,18 @@ namespace Google.Protobuf.Reflection /// that is the responsibility of the accessor. /// </para> /// <para> - /// The value returned by this property will be non-null for all regular fields. However, - /// if a message containing a map field is introspected, the list of nested messages will include + /// In descriptors for generated code, the value returned by this property will be non-null for all + /// regular fields. However, if a message containing a map field is introspected, the list of nested messages will include /// an auto-generated nested key/value pair message for the field. This is not represented in any /// generated type, and the value of the map field itself is represented by a dictionary in the /// reflection API. There are never instances of those "hidden" messages, so no accessor is provided /// and this property will return null. /// </para> + /// <para> + /// In dynamically loaded descriptors, the value returned by this property will current be null; + /// if and when dynamic messages are supported, it will return a suitable accessor to work with + /// them. + /// </para> /// </remarks> public IFieldAccessor Accessor => accessor; @@ -330,7 +335,8 @@ namespace Google.Protobuf.Reflection private IFieldAccessor CreateAccessor() { // If we're given no property name, that's because we really don't want an accessor. - // (At the moment, that means it's a map entry message...) + // This could be because it's a map message, or it could be that we're loading a FileDescriptor dynamically. + // TODO: Support dynamic messages. if (propertyName == null) { return null; diff --git a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs index be94cb10..799f7291 100644 --- a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs @@ -34,6 +34,7 @@ using Google.Protobuf.WellKnownTypes; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; namespace Google.Protobuf.Reflection { @@ -54,12 +55,12 @@ namespace Google.Protobuf.Reflection ForceReflectionInitialization<Value.KindOneofCase>(); } - private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo) + private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo) { SerializedData = descriptorData; DescriptorPool = pool; Proto = proto; - Dependencies = new ReadOnlyCollection<FileDescriptor>((FileDescriptor[]) dependencies.Clone()); + Dependencies = new ReadOnlyCollection<FileDescriptor>(dependencies.ToList()); PublicDependencies = DeterminePublicDependencies(this, proto, dependencies, allowUnknownDependencies); @@ -67,11 +68,11 @@ namespace Google.Protobuf.Reflection MessageTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.MessageType, (message, index) => - new MessageDescriptor(message, this, null, index, generatedCodeInfo.NestedTypes[index])); + new MessageDescriptor(message, this, null, index, generatedCodeInfo?.NestedTypes[index])); EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumType, (enumType, index) => - new EnumDescriptor(enumType, this, null, index, generatedCodeInfo.NestedEnums[index])); + new EnumDescriptor(enumType, this, null, index, generatedCodeInfo?.NestedEnums[index])); Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service, (service, index) => @@ -98,13 +99,9 @@ namespace Google.Protobuf.Reflection /// Extracts public dependencies from direct dependencies. This is a static method despite its /// first parameter, as the value we're in the middle of constructing is only used for exceptions. /// </summary> - private static IList<FileDescriptor> DeterminePublicDependencies(FileDescriptor @this, FileDescriptorProto proto, FileDescriptor[] dependencies, bool allowUnknownDependencies) + private static IList<FileDescriptor> DeterminePublicDependencies(FileDescriptor @this, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, bool allowUnknownDependencies) { - var nameToFileMap = new Dictionary<string, FileDescriptor>(); - foreach (var file in dependencies) - { - nameToFileMap[file.Name] = file; - } + var nameToFileMap = dependencies.ToDictionary(file => file.Name); var publicDependencies = new List<FileDescriptor>(); for (int i = 0; i < proto.PublicDependency.Count; i++) { @@ -114,8 +111,7 @@ namespace Google.Protobuf.Reflection throw new DescriptorValidationException(@this, "Invalid public dependency index."); } string name = proto.Dependency[index]; - FileDescriptor file = nameToFileMap[name]; - if (file == null) + if (!nameToFileMap.TryGetValue(name, out var file)) { if (!allowUnknownDependencies) { @@ -316,6 +312,49 @@ namespace Google.Protobuf.Reflection } /// <summary> + /// Converts the given descriptor binary data into FileDescriptor objects. + /// Note: reflection using the returned FileDescriptors is not currently supported. + /// </summary> + /// <param name="descriptorData">The binary file descriptor proto data. Must not be null, and any + /// dependencies must come before the descriptor which depends on them. (If A depends on B, and B + /// depends on C, then the descriptors must be presented in the order C, B, A.) This is compatible + /// with the order in which protoc provides descriptors to plugins.</param> + /// <returns>The file descriptors corresponding to <paramref name="descriptorData"/>.</returns> + public static IReadOnlyList<FileDescriptor> BuildFromByteStrings(IEnumerable<ByteString> descriptorData) + { + ProtoPreconditions.CheckNotNull(descriptorData, nameof(descriptorData)); + + // TODO: See if we can build a single DescriptorPool instead of building lots of them. + // This will all behave correctly, but it's less efficient than we'd like. + var descriptors = new List<FileDescriptor>(); + var descriptorsByName = new Dictionary<string, FileDescriptor>(); + foreach (var data in descriptorData) + { + var proto = FileDescriptorProto.Parser.ParseFrom(data); + var dependencies = new List<FileDescriptor>(); + foreach (var dependencyName in proto.Dependency) + { + if (!descriptorsByName.TryGetValue(dependencyName, out var dependency)) + { + throw new ArgumentException($"Dependency missing: {dependencyName}"); + } + dependencies.Add(dependency); + } + var pool = new DescriptorPool(dependencies); + FileDescriptor descriptor = new FileDescriptor( + data, proto, dependencies, pool, + allowUnknownDependencies: false, generatedCodeInfo: null); + descriptors.Add(descriptor); + if (descriptorsByName.ContainsKey(descriptor.Name)) + { + throw new ArgumentException($"Duplicate descriptor name: {descriptor.Name}"); + } + descriptorsByName.Add(descriptor.Name, descriptor); + } + return new ReadOnlyCollection<FileDescriptor>(descriptors); + } + + /// <summary> /// Returns a <see cref="System.String" /> that represents this instance. /// </summary> /// <returns> diff --git a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs index 86942acc..dbb6768b 100755..100644 --- a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs @@ -72,23 +72,21 @@ namespace Google.Protobuf.Reflection ClrType = generatedCodeInfo?.ClrType; ContainingType = parent; - // Note use of generatedCodeInfo. rather than generatedCodeInfo?. here... we don't expect - // to see any nested oneofs, types or enums in "not actually generated" code... we do - // expect fields though (for map entry messages). + // If generatedCodeInfo is null, we just won't generate an accessor for any fields. Oneofs = DescriptorUtil.ConvertAndMakeReadOnly( proto.OneofDecl, (oneof, index) => - new OneofDescriptor(oneof, file, this, index, generatedCodeInfo.OneofNames[index])); + new OneofDescriptor(oneof, file, this, index, generatedCodeInfo?.OneofNames[index])); NestedTypes = DescriptorUtil.ConvertAndMakeReadOnly( proto.NestedType, (type, index) => - new MessageDescriptor(type, file, this, index, generatedCodeInfo.NestedTypes[index])); + new MessageDescriptor(type, file, this, index, generatedCodeInfo?.NestedTypes[index])); EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly( proto.EnumType, (type, index) => - new EnumDescriptor(type, file, this, index, generatedCodeInfo.NestedEnums[index])); + new EnumDescriptor(type, file, this, index, generatedCodeInfo?.NestedEnums[index])); fieldsInDeclarationOrder = DescriptorUtil.ConvertAndMakeReadOnly( proto.Field, diff --git a/csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs index 5906c2e3..c026bea6 100644 --- a/csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs @@ -85,6 +85,16 @@ namespace Google.Protobuf.Reflection /// Gets an accessor for reflective access to the values associated with the oneof /// in a particular message. /// </summary> + /// <remarks> + /// <para> + /// In descriptors for generated code, the value returned by this property will always be non-null. + /// </para> + /// <para> + /// In dynamically loaded descriptors, the value returned by this property will current be null; + /// if and when dynamic messages are supported, it will return a suitable accessor to work with + /// them. + /// </para> + /// </remarks> /// <value> /// The accessor used for reflective access. /// </value> @@ -110,6 +120,12 @@ namespace Google.Protobuf.Reflection private OneofAccessor CreateAccessor(string clrName) { + // We won't have a CLR name if this is from a dynamically-loaded FileDescriptor. + // TODO: Support dynamic messages. + if (clrName == null) + { + return null; + } var caseProperty = containingType.ClrType.GetProperty(clrName + "Case"); if (caseProperty == null) { diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Any.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Any.cs index 378b61d4..dd991106 100644 --- a/csharp/src/Google.Protobuf/WellKnownTypes/Any.cs +++ b/csharp/src/Google.Protobuf/WellKnownTypes/Any.cs @@ -159,7 +159,8 @@ namespace Google.Protobuf.WellKnownTypes { private string typeUrl_ = ""; /// <summary> /// A URL/resource name that uniquely identifies the type of the serialized - /// protocol buffer message. The last segment of the URL's path must represent + /// protocol buffer message. This string must contain at least + /// one "/" character. The last segment of the URL's path must represent /// the fully qualified name of the type (as in /// `path/google.protobuf.Duration`). The name should be in a canonical form /// (e.g., leading "." is not accepted). diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/FieldMask.cs b/csharp/src/Google.Protobuf/WellKnownTypes/FieldMask.cs index b73930b2..6ad31a50 100644 --- a/csharp/src/Google.Protobuf/WellKnownTypes/FieldMask.cs +++ b/csharp/src/Google.Protobuf/WellKnownTypes/FieldMask.cs @@ -25,11 +25,11 @@ namespace Google.Protobuf.WellKnownTypes { byte[] descriptorData = global::System.Convert.FromBase64String( string.Concat( "CiBnb29nbGUvcHJvdG9idWYvZmllbGRfbWFzay5wcm90bxIPZ29vZ2xlLnBy", - "b3RvYnVmIhoKCUZpZWxkTWFzaxINCgVwYXRocxgBIAMoCUKJAQoTY29tLmdv", + "b3RvYnVmIhoKCUZpZWxkTWFzaxINCgVwYXRocxgBIAMoCUKMAQoTY29tLmdv", "b2dsZS5wcm90b2J1ZkIORmllbGRNYXNrUHJvdG9QAVo5Z29vZ2xlLmdvbGFu", "Zy5vcmcvZ2VucHJvdG8vcHJvdG9idWYvZmllbGRfbWFzaztmaWVsZF9tYXNr", - "ogIDR1BCqgIeR29vZ2xlLlByb3RvYnVmLldlbGxLbm93blR5cGVzYgZwcm90", - "bzM=")); + "+AEBogIDR1BCqgIeR29vZ2xlLlByb3RvYnVmLldlbGxLbm93blR5cGVzYgZw", + "cm90bzM=")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { }, new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { @@ -108,57 +108,49 @@ namespace Google.Protobuf.WellKnownTypes { /// describe the updated values, the API ignores the values of all /// fields not covered by the mask. /// - /// If a repeated field is specified for an update operation, the existing - /// repeated values in the target resource will be overwritten by the new values. - /// Note that a repeated field is only allowed in the last position of a `paths` - /// string. + /// If a repeated field is specified for an update operation, new values will + /// be appended to the existing repeated field in the target resource. Note that + /// a repeated field is only allowed in the last position of a `paths` string. /// /// If a sub-message is specified in the last position of the field mask for an - /// update operation, then the existing sub-message in the target resource is - /// overwritten. Given the target message: + /// update operation, then new value will be merged into the existing sub-message + /// in the target resource. + /// + /// For example, given the target message: /// /// f { /// b { - /// d : 1 - /// x : 2 + /// d: 1 + /// x: 2 /// } - /// c : 1 + /// c: [1] /// } /// /// And an update message: /// /// f { /// b { - /// d : 10 + /// d: 10 /// } + /// c: [2] /// } /// /// then if the field mask is: /// - /// paths: "f.b" + /// paths: ["f.b", "f.c"] /// /// then the result will be: /// /// f { /// b { - /// d : 10 + /// d: 10 + /// x: 2 /// } - /// c : 1 + /// c: [1, 2] /// } /// - /// However, if the update mask was: - /// - /// paths: "f.b.d" - /// - /// then the result would be: - /// - /// f { - /// b { - /// d : 10 - /// x : 2 - /// } - /// c : 1 - /// } + /// An implementation may provide options to override this default behavior for + /// repeated and message fields. /// /// In order to reset a field's value to the default, the field must /// be in the mask and set to the default value in the provided resource. diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/FieldMaskPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/FieldMaskPartial.cs index 4b0670f6..4b0670f6 100755..100644 --- a/csharp/src/Google.Protobuf/WellKnownTypes/FieldMaskPartial.cs +++ b/csharp/src/Google.Protobuf/WellKnownTypes/FieldMaskPartial.cs diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Timestamp.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Timestamp.cs index ef752be7..d1ab0f89 100644 --- a/csharp/src/Google.Protobuf/WellKnownTypes/Timestamp.cs +++ b/csharp/src/Google.Protobuf/WellKnownTypes/Timestamp.cs @@ -116,7 +116,7 @@ namespace Google.Protobuf.WellKnownTypes { /// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) /// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one /// can use the Joda Time's [`ISODateTimeFormat.dateTime()`]( - /// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime-- + /// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D /// ) to obtain a formatter capable of generating timestamps in this format. /// </summary> public sealed partial class Timestamp : pb::IMessage<Timestamp> { diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs index aa403473..a9251974 100644 --- a/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs +++ b/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs @@ -36,7 +36,7 @@ using System.Text; namespace Google.Protobuf.WellKnownTypes { - public partial class Timestamp : ICustomDiagnosticMessage + public partial class Timestamp : ICustomDiagnosticMessage, IComparable<Timestamp> { private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); // Constants determined programmatically, but then hard-coded so they can be constant expressions. @@ -223,6 +223,109 @@ namespace Google.Protobuf.WellKnownTypes } /// <summary> + /// Given another timestamp, returns 0 if the timestamps are equivalent, -1 if this timestamp precedes the other, and 1 otherwise + /// </summary> + /// <remarks> + /// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results. + /// </remarks> + /// <param name="other">Timestamp to compare</param> + /// <returns>an integer indicating whether this timestamp precedes or follows the other</returns> + public int CompareTo(Timestamp other) + { + return other == null ? 1 + : Seconds < other.Seconds ? -1 + : Seconds > other.Seconds ? 1 + : Nanos < other.Nanos ? -1 + : Nanos > other.Nanos ? 1 + : 0; + } + + /// <summary> + /// Compares two timestamps and returns whether the first is less than (chronologically precedes) the second + /// </summary> + /// <remarks> + /// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results. + /// </remarks> + /// <param name="a"></param> + /// <param name="b"></param> + /// <returns>true if a precedes b</returns> + public static bool operator <(Timestamp a, Timestamp b) + { + return a.CompareTo(b) < 0; + } + + /// <summary> + /// Compares two timestamps and returns whether the first is greater than (chronologically follows) the second + /// </summary> + /// <remarks> + /// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results. + /// </remarks> + /// <param name="a"></param> + /// <param name="b"></param> + /// <returns>true if a follows b</returns> + public static bool operator >(Timestamp a, Timestamp b) + { + return a.CompareTo(b) > 0; + } + + /// <summary> + /// Compares two timestamps and returns whether the first is less than (chronologically precedes) the second + /// </summary> + /// <remarks> + /// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results. + /// </remarks> + /// <param name="a"></param> + /// <param name="b"></param> + /// <returns>true if a precedes b</returns> + public static bool operator <=(Timestamp a, Timestamp b) + { + return a.CompareTo(b) <= 0; + } + + /// <summary> + /// Compares two timestamps and returns whether the first is greater than (chronologically follows) the second + /// </summary> + /// <remarks> + /// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results. + /// </remarks> + /// <param name="a"></param> + /// <param name="b"></param> + /// <returns>true if a follows b</returns> + public static bool operator >=(Timestamp a, Timestamp b) + { + return a.CompareTo(b) >= 0; + } + + + /// <summary> + /// Returns whether two timestamps are equivalent + /// </summary> + /// <remarks> + /// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results. + /// </remarks> + /// <param name="a"></param> + /// <param name="b"></param> + /// <returns>true if the two timestamps refer to the same nanosecond</returns> + public static bool operator ==(Timestamp a, Timestamp b) + { + return ReferenceEquals(a, b) || (a is null ? (b is null ? true : false) : a.Equals(b)); + } + + /// <summary> + /// Returns whether two timestamps differ + /// </summary> + /// <remarks> + /// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results. + /// </remarks> + /// <param name="a"></param> + /// <param name="b"></param> + /// <returns>true if the two timestamps differ</returns> + public static bool operator !=(Timestamp a, Timestamp b) + { + return !(a == b); + } + + /// <summary> /// Returns a string representation of this <see cref="Timestamp"/> for diagnostic purposes. /// </summary> /// <remarks> |