Support custom options in C#
This consists of: - Changing the codegen for the fixed set of options protos, to parse unknown fields instead of skipping them - Add a new CustomOptions type in the C# support library - Expose CustomOptions properties from the immutable proto wrappers in the support library Only single-value options are currently supported, and fetching options values requires getting the type right and knowing the field number. Both of these can be addressed at a later time. Fixes #2143, at least as a first pass.
This commit is contained in:
parent
eed9951991
commit
047575f20c
@ -60,6 +60,7 @@ csharp_EXTRA_DIST= \
|
||||
csharp/keys/Google.Protobuf.public.snk \
|
||||
csharp/keys/Google.Protobuf.snk \
|
||||
csharp/keys/README.md \
|
||||
csharp/protos/unittest_custom_options_proto3.proto \
|
||||
csharp/protos/unittest_issues.proto \
|
||||
csharp/src/AddressBook/AddPerson.cs \
|
||||
csharp/src/AddressBook/Addressbook.cs \
|
||||
@ -92,6 +93,7 @@ csharp_EXTRA_DIST= \
|
||||
csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs \
|
||||
csharp/src/Google.Protobuf.Test/JsonParserTest.cs \
|
||||
csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs \
|
||||
csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs \
|
||||
csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs \
|
||||
csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs \
|
||||
csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs \
|
||||
@ -101,6 +103,7 @@ csharp_EXTRA_DIST= \
|
||||
csharp/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs \
|
||||
csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs \
|
||||
csharp/src/Google.Protobuf.Test/TestProtos/TestMessagesProto3.cs \
|
||||
csharp/src/Google.Protobuf.Test/TestProtos/UnittestCustomOptionsProto3.cs \
|
||||
csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportProto3.cs \
|
||||
csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportPublicProto3.cs \
|
||||
csharp/src/Google.Protobuf.Test/TestProtos/UnittestIssues.cs \
|
||||
@ -140,6 +143,7 @@ csharp_EXTRA_DIST= \
|
||||
csharp/src/Google.Protobuf/MessageParser.cs \
|
||||
csharp/src/Google.Protobuf/ProtoPreconditions.cs \
|
||||
csharp/src/Google.Protobuf/Properties/AssemblyInfo.cs \
|
||||
csharp/src/Google.Protobuf/Reflection/CustomOptions.cs \
|
||||
csharp/src/Google.Protobuf/Reflection/Descriptor.cs \
|
||||
csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs \
|
||||
csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs \
|
||||
|
@ -50,9 +50,10 @@ $PROTOC -Isrc --csharp_out=csharp/src/Google.Protobuf.Test \
|
||||
src/google/protobuf/unittest_well_known_types.proto
|
||||
|
||||
# Different base namespace to the protos above
|
||||
$PROTOC -Icsharp/protos --csharp_out=csharp/src/Google.Protobuf.Test \
|
||||
$PROTOC -Isrc -Icsharp/protos --csharp_out=csharp/src/Google.Protobuf.Test \
|
||||
--csharp_opt=base_namespace=UnitTest.Issues \
|
||||
csharp/protos/unittest_issues.proto
|
||||
csharp/protos/unittest_issues.proto \
|
||||
csharp/protos/unittest_custom_options_proto3.proto
|
||||
|
||||
# Don't specify a base namespace at all; we just want to make sure the
|
||||
# results end up in TestProtos.
|
||||
|
338
csharp/protos/unittest_custom_options_proto3.proto
Normal file
338
csharp/protos/unittest_custom_options_proto3.proto
Normal file
@ -0,0 +1,338 @@
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Author: benjy@google.com (Benjy Weinberger)
|
||||
// Based on original Protocol Buffers design by
|
||||
// Sanjay Ghemawat, Jeff Dean, and others.
|
||||
//
|
||||
// A proto file used to test the "custom options" feature of google.protobuf.
|
||||
|
||||
// This file is based on unittest_custom_options.proto in
|
||||
// src/google/protobuf, but is modified for proto3. It could
|
||||
// potentially be moved into src/google/protobuf, but currently C#
|
||||
// is the only language that really needs it, as we don't support
|
||||
// proto2 syntax. It's cut down significantly as proto3 only supports
|
||||
// extensions for options.
|
||||
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
// A custom file option (defined below).
|
||||
option (file_opt1) = 9876543210;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
// We don't put this in a package within proto2 because we need to make sure
|
||||
// that the generated code doesn't depend on being in the proto2 namespace.
|
||||
package protobuf_unittest;
|
||||
option csharp_namespace = "UnitTest.Issues.TestProtos";
|
||||
|
||||
// Some simple test custom options of various types.
|
||||
|
||||
extend google.protobuf.FileOptions {
|
||||
uint64 file_opt1 = 7736974;
|
||||
}
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
int32 message_opt1 = 7739036;
|
||||
}
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
fixed64 field_opt1 = 7740936;
|
||||
}
|
||||
|
||||
// See https://github.com/google/protobuf/issues/2316
|
||||
// extend google.protobuf.OneofOptions {
|
||||
// int32 oneof_opt1 = 7740111;
|
||||
// }
|
||||
|
||||
extend google.protobuf.EnumOptions {
|
||||
sfixed32 enum_opt1 = 7753576;
|
||||
}
|
||||
|
||||
extend google.protobuf.EnumValueOptions {
|
||||
int32 enum_value_opt1 = 1560678;
|
||||
}
|
||||
|
||||
extend google.protobuf.ServiceOptions {
|
||||
sint64 service_opt1 = 7887650;
|
||||
}
|
||||
|
||||
enum MethodOpt1 {
|
||||
METHODOPT1_UNSPECIFIED = 0;
|
||||
METHODOPT1_VAL1 = 1;
|
||||
METHODOPT1_VAL2 = 2;
|
||||
}
|
||||
|
||||
extend google.protobuf.MethodOptions {
|
||||
MethodOpt1 method_opt1 = 7890860;
|
||||
}
|
||||
|
||||
// A test message with custom options at all possible locations (and also some
|
||||
// regular options, to make sure they interact nicely).
|
||||
message TestMessageWithCustomOptions {
|
||||
option message_set_wire_format = false;
|
||||
|
||||
option (message_opt1) = -56;
|
||||
|
||||
string field1 = 1 [ctype=CORD,
|
||||
(field_opt1)=8765432109];
|
||||
|
||||
oneof AnOneof {
|
||||
// See https://github.com/google/protobuf/issues/2316
|
||||
// option (oneof_opt1) = -99;
|
||||
int32 oneof_field = 2;
|
||||
}
|
||||
|
||||
enum AnEnum {
|
||||
option (enum_opt1) = -789;
|
||||
ANENUM_UNSPECIFIED = 0;
|
||||
ANENUM_VAL1 = 1;
|
||||
ANENUM_VAL2 = 2 [(enum_value_opt1) = 123];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// A test RPC service with custom options at all possible locations (and also
|
||||
// some regular options, to make sure they interact nicely).
|
||||
message CustomOptionFooRequest {
|
||||
}
|
||||
|
||||
message CustomOptionFooResponse {
|
||||
}
|
||||
|
||||
message CustomOptionFooClientMessage {
|
||||
}
|
||||
|
||||
message CustomOptionFooServerMessage {
|
||||
}
|
||||
|
||||
service TestServiceWithCustomOptions {
|
||||
option (service_opt1) = -9876543210;
|
||||
|
||||
rpc Foo(CustomOptionFooRequest) returns (CustomOptionFooResponse) {
|
||||
option (method_opt1) = METHODOPT1_VAL2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Options of every possible field type, so we can test them all exhaustively.
|
||||
|
||||
message DummyMessageContainingEnum {
|
||||
enum TestEnumType {
|
||||
TEST_OPTION_ENUM_UNSPECIFIED = 0;
|
||||
TEST_OPTION_ENUM_TYPE1 = 22;
|
||||
TEST_OPTION_ENUM_TYPE2 = -23;
|
||||
}
|
||||
}
|
||||
|
||||
message DummyMessageInvalidAsOptionType {
|
||||
}
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
bool bool_opt = 7706090;
|
||||
int32 int32_opt = 7705709;
|
||||
int64 int64_opt = 7705542;
|
||||
uint32 uint32_opt = 7704880;
|
||||
uint64 uint64_opt = 7702367;
|
||||
sint32 sint32_opt = 7701568;
|
||||
sint64 sint64_opt = 7700863;
|
||||
fixed32 fixed32_opt = 7700307;
|
||||
fixed64 fixed64_opt = 7700194;
|
||||
sfixed32 sfixed32_opt = 7698645;
|
||||
sfixed64 sfixed64_opt = 7685475;
|
||||
float float_opt = 7675390;
|
||||
double double_opt = 7673293;
|
||||
string string_opt = 7673285;
|
||||
bytes bytes_opt = 7673238;
|
||||
DummyMessageContainingEnum.TestEnumType enum_opt = 7673233;
|
||||
DummyMessageInvalidAsOptionType message_type_opt = 7665967;
|
||||
}
|
||||
|
||||
message CustomOptionMinIntegerValues {
|
||||
option (bool_opt) = false;
|
||||
option (int32_opt) = -0x80000000;
|
||||
option (int64_opt) = -0x8000000000000000;
|
||||
option (uint32_opt) = 0;
|
||||
option (uint64_opt) = 0;
|
||||
option (sint32_opt) = -0x80000000;
|
||||
option (sint64_opt) = -0x8000000000000000;
|
||||
option (fixed32_opt) = 0;
|
||||
option (fixed64_opt) = 0;
|
||||
option (sfixed32_opt) = -0x80000000;
|
||||
option (sfixed64_opt) = -0x8000000000000000;
|
||||
}
|
||||
|
||||
message CustomOptionMaxIntegerValues {
|
||||
option (bool_opt) = true;
|
||||
option (int32_opt) = 0x7FFFFFFF;
|
||||
option (int64_opt) = 0x7FFFFFFFFFFFFFFF;
|
||||
option (uint32_opt) = 0xFFFFFFFF;
|
||||
option (uint64_opt) = 0xFFFFFFFFFFFFFFFF;
|
||||
option (sint32_opt) = 0x7FFFFFFF;
|
||||
option (sint64_opt) = 0x7FFFFFFFFFFFFFFF;
|
||||
option (fixed32_opt) = 0xFFFFFFFF;
|
||||
option (fixed64_opt) = 0xFFFFFFFFFFFFFFFF;
|
||||
option (sfixed32_opt) = 0x7FFFFFFF;
|
||||
option (sfixed64_opt) = 0x7FFFFFFFFFFFFFFF;
|
||||
}
|
||||
|
||||
message CustomOptionOtherValues {
|
||||
option (int32_opt) = -100; // To test sign-extension.
|
||||
option (float_opt) = 12.3456789;
|
||||
option (double_opt) = 1.234567890123456789;
|
||||
option (string_opt) = "Hello, \"World\"";
|
||||
option (bytes_opt) = "Hello\0World";
|
||||
option (enum_opt) = TEST_OPTION_ENUM_TYPE2;
|
||||
}
|
||||
|
||||
message SettingRealsFromPositiveInts {
|
||||
option (float_opt) = 12;
|
||||
option (double_opt) = 154;
|
||||
}
|
||||
|
||||
message SettingRealsFromNegativeInts {
|
||||
option (float_opt) = -12;
|
||||
option (double_opt) = -154;
|
||||
}
|
||||
|
||||
// Options of complex message types, themselves combined and extended in
|
||||
// various ways.
|
||||
|
||||
message ComplexOptionType1 {
|
||||
int32 foo = 1;
|
||||
int32 foo2 = 2;
|
||||
int32 foo3 = 3;
|
||||
repeated int32 foo4 = 4;
|
||||
}
|
||||
|
||||
message ComplexOptionType2 {
|
||||
ComplexOptionType1 bar = 1;
|
||||
int32 baz = 2;
|
||||
|
||||
message ComplexOptionType4 {
|
||||
int32 waldo = 1;
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
ComplexOptionType4 complex_opt4 = 7633546;
|
||||
}
|
||||
}
|
||||
|
||||
ComplexOptionType4 fred = 3;
|
||||
repeated ComplexOptionType4 barney = 4;
|
||||
}
|
||||
|
||||
message ComplexOptionType3 {
|
||||
int32 qux = 1;
|
||||
}
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
protobuf_unittest.ComplexOptionType1 complex_opt1 = 7646756;
|
||||
ComplexOptionType2 complex_opt2 = 7636949;
|
||||
ComplexOptionType3 complex_opt3 = 7636463;
|
||||
}
|
||||
|
||||
// Note that we try various different ways of naming the same extension.
|
||||
message VariousComplexOptions {
|
||||
option (.protobuf_unittest.complex_opt1).foo = 42;
|
||||
option (protobuf_unittest.complex_opt1).foo4 = 99;
|
||||
option (protobuf_unittest.complex_opt1).foo4 = 88;
|
||||
option (complex_opt2).baz = 987;
|
||||
option (complex_opt2).bar.foo = 743;
|
||||
option (ComplexOptionType2.ComplexOptionType4.complex_opt4).waldo = 1971;
|
||||
option (complex_opt2).fred.waldo = 321;
|
||||
option (complex_opt2).barney = { waldo: 101 };
|
||||
option (complex_opt2).barney = { waldo: 212 };
|
||||
option (protobuf_unittest.complex_opt3).qux = 9;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Definitions for testing aggregate option parsing.
|
||||
// See descriptor_unittest.cc.
|
||||
|
||||
// A helper type used to test aggregate option parsing
|
||||
message Aggregate {
|
||||
int32 i = 1;
|
||||
string s = 2;
|
||||
|
||||
// A nested object
|
||||
Aggregate sub = 3;
|
||||
}
|
||||
|
||||
// Allow Aggregate to be used as an option at all possible locations
|
||||
// in the .proto grammer.
|
||||
extend google.protobuf.FileOptions { Aggregate fileopt = 15478479; }
|
||||
extend google.protobuf.MessageOptions { Aggregate msgopt = 15480088; }
|
||||
extend google.protobuf.FieldOptions { Aggregate fieldopt = 15481374; }
|
||||
extend google.protobuf.EnumOptions { Aggregate enumopt = 15483218; }
|
||||
extend google.protobuf.EnumValueOptions { Aggregate enumvalopt = 15486921; }
|
||||
extend google.protobuf.ServiceOptions { Aggregate serviceopt = 15497145; }
|
||||
extend google.protobuf.MethodOptions { Aggregate methodopt = 15512713; }
|
||||
|
||||
// Try using AggregateOption at different points in the proto grammar
|
||||
option (fileopt) = {
|
||||
s: 'FileAnnotation'
|
||||
// Also test the handling of comments
|
||||
/* of both types */ i: 100
|
||||
|
||||
sub { s: 'NestedFileAnnotation' }
|
||||
};
|
||||
|
||||
message AggregateMessage {
|
||||
option (msgopt) = { i:101 s:'MessageAnnotation' };
|
||||
int32 fieldname = 1 [(fieldopt) = { s:'FieldAnnotation' }];
|
||||
}
|
||||
|
||||
service AggregateService {
|
||||
option (serviceopt) = { s:'ServiceAnnotation' };
|
||||
rpc Method (AggregateMessage) returns (AggregateMessage) {
|
||||
option (methodopt) = { s:'MethodAnnotation' };
|
||||
}
|
||||
}
|
||||
|
||||
enum AggregateEnum {
|
||||
option (enumopt) = { s:'EnumAnnotation' };
|
||||
UNSPECIFIED = 0;
|
||||
VALUE = 1 [(enumvalopt) = { s:'EnumValueAnnotation' }];
|
||||
}
|
||||
|
||||
// Test custom options for nested type.
|
||||
message NestedOptionType {
|
||||
message NestedMessage {
|
||||
option (message_opt1) = 1001;
|
||||
int32 nested_field = 1 [(field_opt1) = 1002];
|
||||
}
|
||||
enum NestedEnum {
|
||||
UNSPECIFIED = 0;
|
||||
option (enum_opt1) = 1003;
|
||||
NESTED_ENUM_VALUE = 1 [(enum_value_opt1) = 1004];
|
||||
}
|
||||
}
|
267
csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
Normal file
267
csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
Normal file
@ -0,0 +1,267 @@
|
||||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#endregion
|
||||
|
||||
using Google.Protobuf.Reflection;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using NUnit.Framework;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnitTest.Issues.TestProtos;
|
||||
using static Google.Protobuf.WireFormat;
|
||||
using static UnitTest.Issues.TestProtos.ComplexOptionType2.Types;
|
||||
using static UnitTest.Issues.TestProtos.DummyMessageContainingEnum.Types;
|
||||
using static Google.Protobuf.Test.Reflection.CustomOptionNumber;
|
||||
|
||||
namespace Google.Protobuf.Test.Reflection
|
||||
{
|
||||
// Internal enum to allow us to use "using static" for convenience.
|
||||
// These are the options defined in unittest_custom_options_proto3.proto
|
||||
internal enum CustomOptionNumber
|
||||
{
|
||||
FileOpt1 = 7736974,
|
||||
MessageOpt1 = 7739036,
|
||||
FieldOpt1 = 7740936,
|
||||
EnumOpt1 = 7753576,
|
||||
EnumValueOpt1 = 1560678,
|
||||
ServiceOpt1 = 7887650,
|
||||
MethodOpt1 = 7890860,
|
||||
|
||||
// All message options...
|
||||
BoolOpt = 7706090,
|
||||
Int32Opt = 7705709,
|
||||
Int64Opt = 7705542,
|
||||
UInt32Opt = 7704880,
|
||||
UInt64Opt = 7702367,
|
||||
SInt32Opt = 7701568,
|
||||
SInt64Opt = 7700863,
|
||||
Fixed32Opt = 7700307,
|
||||
Fixed64Opt = 7700194,
|
||||
SFixed32Opt = 7698645,
|
||||
SFixed64Opt = 7685475,
|
||||
FloatOpt = 7675390,
|
||||
DoubleOpt = 7673293,
|
||||
StringOpt = 7673285,
|
||||
BytesOpt = 7673238,
|
||||
EnumOpt = 7673233,
|
||||
MessageTypeOpt = 7665967,
|
||||
|
||||
// Miscellaneous
|
||||
ComplexOpt4 = 7633546,
|
||||
ComplexOpt1 = 7646756,
|
||||
ComplexOpt2 = 7636949,
|
||||
ComplexOpt3 = 7636463,
|
||||
|
||||
// Aggregates
|
||||
AggregateFileOpt = 15478479,
|
||||
AggregateMsgOpt = 15480088,
|
||||
AggregateFieldOpt = 15481374,
|
||||
AggregateEnumOpt = 15483218,
|
||||
AggregateEnumValueOpt = 15486921,
|
||||
AggregateServiceOpt = 15497145,
|
||||
AggregateMethodOpt = 15512713,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The majority of the testing here is done via parsed descriptors. That's simpler to
|
||||
/// achieve (and more important) than constructing a CodedInputStream manually.
|
||||
/// </summary>
|
||||
public class CustomOptionsTest
|
||||
{
|
||||
delegate bool OptionFetcher<T>(int field, out T value);
|
||||
|
||||
[Test]
|
||||
public void EmptyOptionsIsShared()
|
||||
{
|
||||
var structOptions = Struct.Descriptor.CustomOptions;
|
||||
var timestampOptions = Struct.Descriptor.CustomOptions;
|
||||
Assert.AreSame(structOptions, timestampOptions);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SimpleIntegerTest()
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
var output = new CodedOutputStream(stream);
|
||||
output.WriteTag(MakeTag(1, WireType.Varint));
|
||||
output.WriteInt32(1234567);
|
||||
output.Flush();
|
||||
stream.Position = 0;
|
||||
var input = new CodedInputStream(stream);
|
||||
input.ReadTag();
|
||||
|
||||
var options = CustomOptions.Empty;
|
||||
options = options.ReadOrSkipUnknownField(input);
|
||||
|
||||
int intValue;
|
||||
Assert.True(options.TryGetInt32(1, out intValue));
|
||||
Assert.AreEqual(1234567, intValue);
|
||||
|
||||
string stringValue;
|
||||
// No ByteString stored values
|
||||
Assert.False(options.TryGetString(1, out stringValue));
|
||||
// Nothing stored for field 2
|
||||
Assert.False(options.TryGetInt32(2, out intValue));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SimpleStringTest()
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
var output = new CodedOutputStream(stream);
|
||||
output.WriteTag(MakeTag(1, WireType.LengthDelimited));
|
||||
output.WriteString("value");
|
||||
output.Flush();
|
||||
stream.Position = 0;
|
||||
var input = new CodedInputStream(stream);
|
||||
input.ReadTag();
|
||||
|
||||
var options = CustomOptions.Empty;
|
||||
options = options.ReadOrSkipUnknownField(input);
|
||||
|
||||
string stringValue;
|
||||
Assert.True(options.TryGetString(1, out stringValue));
|
||||
Assert.AreEqual("value", stringValue);
|
||||
|
||||
int intValue;
|
||||
// No numeric stored values
|
||||
Assert.False(options.TryGetInt32(1, out intValue));
|
||||
// Nothing stored for field 2
|
||||
Assert.False(options.TryGetString(2, out stringValue));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScalarOptions()
|
||||
{
|
||||
var options = CustomOptionOtherValues.Descriptor.CustomOptions;
|
||||
AssertOption(-100, options.TryGetInt32, Int32Opt);
|
||||
AssertOption(12.3456789f, options.TryGetFloat, FloatOpt);
|
||||
AssertOption(1.234567890123456789d, options.TryGetDouble, DoubleOpt);
|
||||
AssertOption("Hello, \"World\"", options.TryGetString, StringOpt);
|
||||
AssertOption(ByteString.CopyFromUtf8("Hello\0World"), options.TryGetBytes, BytesOpt);
|
||||
AssertOption((int) TestEnumType.TestOptionEnumType2, options.TryGetInt32, EnumOpt);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MessageOptions()
|
||||
{
|
||||
var options = VariousComplexOptions.Descriptor.CustomOptions;
|
||||
AssertOption(new ComplexOptionType1 { Foo = 42, Foo4 = { 99, 88 } }, options.TryGetMessage, ComplexOpt1);
|
||||
AssertOption(new ComplexOptionType2
|
||||
{
|
||||
Baz = 987, Bar = new ComplexOptionType1 { Foo = 743 },
|
||||
Fred = new ComplexOptionType4 { Waldo = 321 },
|
||||
Barney = { new ComplexOptionType4 { Waldo = 101 }, new ComplexOptionType4 { Waldo = 212 } }
|
||||
},
|
||||
options.TryGetMessage, ComplexOpt2);
|
||||
AssertOption(new ComplexOptionType3 { Qux = 9 }, options.TryGetMessage, ComplexOpt3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OptionLocations()
|
||||
{
|
||||
var fileOptions = UnittestCustomOptionsProto3Reflection.Descriptor.CustomOptions;
|
||||
AssertOption(9876543210UL, fileOptions.TryGetUInt64, FileOpt1);
|
||||
|
||||
var messageOptions = TestMessageWithCustomOptions.Descriptor.CustomOptions;
|
||||
AssertOption(-56, messageOptions.TryGetInt32, MessageOpt1);
|
||||
|
||||
var fieldOptions = TestMessageWithCustomOptions.Descriptor.Fields["field1"] .CustomOptions;
|
||||
AssertOption(8765432109UL, fieldOptions.TryGetFixed64, FieldOpt1);
|
||||
|
||||
var enumOptions = TestMessageWithCustomOptions.Descriptor.EnumTypes[0].CustomOptions;
|
||||
AssertOption(-789, enumOptions.TryGetSFixed32, EnumOpt1);
|
||||
|
||||
var enumValueOptions = TestMessageWithCustomOptions.Descriptor.EnumTypes[0].FindValueByNumber(2).CustomOptions;
|
||||
AssertOption(123, enumValueOptions.TryGetInt32, EnumValueOpt1);
|
||||
|
||||
var service = UnittestCustomOptionsProto3Reflection.Descriptor.Services
|
||||
.Single(s => s.Name == "TestServiceWithCustomOptions");
|
||||
var serviceOptions = service.CustomOptions;
|
||||
AssertOption(-9876543210, serviceOptions.TryGetSInt64, ServiceOpt1);
|
||||
|
||||
var methodOptions = service.Methods[0].CustomOptions;
|
||||
AssertOption((int) UnitTest.Issues.TestProtos.MethodOpt1.Val2, methodOptions.TryGetInt32, CustomOptionNumber.MethodOpt1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MinValues()
|
||||
{
|
||||
var options = CustomOptionMinIntegerValues.Descriptor.CustomOptions;
|
||||
AssertOption(false, options.TryGetBool, BoolOpt);
|
||||
AssertOption(int.MinValue, options.TryGetInt32, Int32Opt);
|
||||
AssertOption(long.MinValue, options.TryGetInt64, Int64Opt);
|
||||
AssertOption(uint.MinValue, options.TryGetUInt32, UInt32Opt);
|
||||
AssertOption(ulong.MinValue, options.TryGetUInt64, UInt64Opt);
|
||||
AssertOption(int.MinValue, options.TryGetSInt32, SInt32Opt);
|
||||
AssertOption(long.MinValue, options.TryGetSInt64, SInt64Opt);
|
||||
AssertOption(uint.MinValue, options.TryGetUInt32, Fixed32Opt);
|
||||
AssertOption(ulong.MinValue, options.TryGetUInt64, Fixed64Opt);
|
||||
AssertOption(int.MinValue, options.TryGetInt32, SFixed32Opt);
|
||||
AssertOption(long.MinValue, options.TryGetInt64, SFixed64Opt);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MaxValues()
|
||||
{
|
||||
var options = CustomOptionMaxIntegerValues.Descriptor.CustomOptions;
|
||||
AssertOption(true, options.TryGetBool, BoolOpt);
|
||||
AssertOption(int.MaxValue, options.TryGetInt32, Int32Opt);
|
||||
AssertOption(long.MaxValue, options.TryGetInt64, Int64Opt);
|
||||
AssertOption(uint.MaxValue, options.TryGetUInt32, UInt32Opt);
|
||||
AssertOption(ulong.MaxValue, options.TryGetUInt64, UInt64Opt);
|
||||
AssertOption(int.MaxValue, options.TryGetSInt32, SInt32Opt);
|
||||
AssertOption(long.MaxValue, options.TryGetSInt64, SInt64Opt);
|
||||
AssertOption(uint.MaxValue, options.TryGetFixed32, Fixed32Opt);
|
||||
AssertOption(ulong.MaxValue, options.TryGetFixed64, Fixed64Opt);
|
||||
AssertOption(int.MaxValue, options.TryGetSFixed32, SFixed32Opt);
|
||||
AssertOption(long.MaxValue, options.TryGetSFixed64, SFixed64Opt);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AggregateOptions()
|
||||
{
|
||||
// Just two examples
|
||||
var messageOptions = AggregateMessage.Descriptor.CustomOptions;
|
||||
AssertOption(new Aggregate { I = 101, S = "MessageAnnotation" }, messageOptions.TryGetMessage, AggregateMsgOpt);
|
||||
|
||||
var fieldOptions = AggregateMessage.Descriptor.Fields["fieldname"].CustomOptions;
|
||||
AssertOption(new Aggregate { S = "FieldAnnotation" }, fieldOptions.TryGetMessage, AggregateFieldOpt);
|
||||
}
|
||||
|
||||
private void AssertOption<T>(T expected, OptionFetcher<T> fetcher, CustomOptionNumber field)
|
||||
{
|
||||
T actual;
|
||||
Assert.IsTrue(fetcher((int) field, out actual));
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
390
csharp/src/Google.Protobuf/Reflection/CustomOptions.cs
Normal file
390
csharp/src/Google.Protobuf/Reflection/CustomOptions.cs
Normal file
@ -0,0 +1,390 @@
|
||||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Container for a set of custom options specified within a message, field etc.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This type is publicly immutable, but internally mutable. It is only populated
|
||||
/// by the descriptor parsing code - by the time any user code is able to see an instance,
|
||||
/// it will be fully initialized.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If an option is requested using the incorrect method, an answer may still be returned: all
|
||||
/// of the numeric types are represented internally using 64-bit integers, for example. It is up to
|
||||
/// the caller to ensure that they make the appropriate method call for the option they're interested in.
|
||||
/// Note that enum options are simply stored as integers, so the value should be fetched using
|
||||
/// <see cref="TryGetInt32(int, out int)"/> and then cast appropriately.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Repeated options are currently not supported. Asking for a single value of an option
|
||||
/// which was actually repeated will return the last value, except for message types where
|
||||
/// all the set values are merged together.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class CustomOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Singleton for all descriptors with an empty set of options.
|
||||
/// </summary>
|
||||
internal static readonly CustomOptions Empty = new CustomOptions();
|
||||
|
||||
/// <summary>
|
||||
/// A sequence of values per field. This needs to be per field rather than per tag to allow correct deserialization
|
||||
/// of repeated fields which could be "int, ByteString, int" - unlikely as that is. The fact that values are boxed
|
||||
/// is unfortunate; we might be able to use a struct instead, and we could combine uint and ulong values.
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, List<FieldValue>> valuesByField = new Dictionary<int, List<FieldValue>>();
|
||||
|
||||
private CustomOptions() { }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a Boolean value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetBool(int field, out bool value)
|
||||
{
|
||||
ulong? tmp = GetLastNumericValue(field);
|
||||
value = tmp == 1UL;
|
||||
return tmp != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 32-bit integer value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetInt32(int field, out int value)
|
||||
{
|
||||
ulong? tmp = GetLastNumericValue(field);
|
||||
value = (int) tmp.GetValueOrDefault();
|
||||
return tmp != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 64-bit integer value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetInt64(int field, out long value)
|
||||
{
|
||||
ulong? tmp = GetLastNumericValue(field);
|
||||
value = (long) tmp.GetValueOrDefault();
|
||||
return tmp != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an unsigned 32-bit integer value for the specified option field,
|
||||
/// assuming a fixed-length representation.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetFixed32(int field, out uint value) => TryGetUInt32(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an unsigned 64-bit integer value for the specified option field,
|
||||
/// assuming a fixed-length representation.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetFixed64(int field, out ulong value) => TryGetUInt64(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 32-bit integer value for the specified option field,
|
||||
/// assuming a fixed-length representation.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetSFixed32(int field, out int value) => TryGetInt32(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 64-bit integer value for the specified option field,
|
||||
/// assuming a fixed-length representation.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetSFixed64(int field, out long value) => TryGetInt64(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 32-bit integer value for the specified option field,
|
||||
/// assuming a zigzag encoding.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetSInt32(int field, out int value)
|
||||
{
|
||||
ulong? tmp = GetLastNumericValue(field);
|
||||
value = CodedInputStream.DecodeZigZag32((uint) tmp.GetValueOrDefault());
|
||||
return tmp != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 64-bit integer value for the specified option field,
|
||||
/// assuming a zigzag encoding.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetSInt64(int field, out long value)
|
||||
{
|
||||
ulong? tmp = GetLastNumericValue(field);
|
||||
value = CodedInputStream.DecodeZigZag64(tmp.GetValueOrDefault());
|
||||
return tmp != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an unsigned 32-bit integer value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetUInt32(int field, out uint value)
|
||||
{
|
||||
ulong? tmp = GetLastNumericValue(field);
|
||||
value = (uint) tmp.GetValueOrDefault();
|
||||
return tmp != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an unsigned 64-bit integer value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetUInt64(int field, out ulong value)
|
||||
{
|
||||
ulong? tmp = GetLastNumericValue(field);
|
||||
value = tmp.GetValueOrDefault();
|
||||
return tmp != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a 32-bit floating point value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetFloat(int field, out float value)
|
||||
{
|
||||
ulong? tmp = GetLastNumericValue(field);
|
||||
int int32 = (int) tmp.GetValueOrDefault();
|
||||
byte[] bytes = BitConverter.GetBytes(int32);
|
||||
value = BitConverter.ToSingle(bytes, 0);
|
||||
return tmp != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a 64-bit floating point value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetDouble(int field, out double value)
|
||||
{
|
||||
ulong? tmp = GetLastNumericValue(field);
|
||||
value = BitConverter.Int64BitsToDouble((long) tmp.GetValueOrDefault());
|
||||
return tmp != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a string value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetString(int field, out string value)
|
||||
{
|
||||
ByteString bytes = GetLastByteStringValue(field);
|
||||
value = bytes?.ToStringUtf8();
|
||||
return bytes != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a bytes value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetBytes(int field, out ByteString value)
|
||||
{
|
||||
ByteString bytes = GetLastByteStringValue(field);
|
||||
value = bytes;
|
||||
return bytes != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a message value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetMessage<T>(int field, out T value) where T : class, IMessage, new()
|
||||
{
|
||||
value = null;
|
||||
List<FieldValue> values;
|
||||
if (!valuesByField.TryGetValue(field, out values))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (FieldValue fieldValue in values)
|
||||
{
|
||||
if (fieldValue.ByteString != null)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
value = new T();
|
||||
}
|
||||
value.MergeFrom(fieldValue.ByteString);
|
||||
}
|
||||
}
|
||||
return value != null;
|
||||
}
|
||||
|
||||
private ulong? GetLastNumericValue(int field)
|
||||
{
|
||||
List<FieldValue> values;
|
||||
if (!valuesByField.TryGetValue(field, out values))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (int i = values.Count - 1; i >= 0; i--)
|
||||
{
|
||||
// A non-bytestring value is a numeric value
|
||||
if (values[i].ByteString == null)
|
||||
{
|
||||
return values[i].Number;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ByteString GetLastByteStringValue(int field)
|
||||
{
|
||||
List<FieldValue> values;
|
||||
if (!valuesByField.TryGetValue(field, out values))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (int i = values.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (values[i].ByteString != null)
|
||||
{
|
||||
return values[i].ByteString;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unknown field, either parsing it and storing it or skipping it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the current set of options is empty and we manage to read a field, a new set of options
|
||||
/// will be created and returned. Otherwise, the return value is <c>this</c>. This allows
|
||||
/// us to start with a singleton empty set of options and just create new ones where necessary.
|
||||
/// </remarks>
|
||||
/// <param name="input">Input stream to read from. </param>
|
||||
/// <returns>The resulting set of custom options, either <c>this</c> or a new set.</returns>
|
||||
internal CustomOptions ReadOrSkipUnknownField(CodedInputStream input)
|
||||
{
|
||||
var tag = input.LastTag;
|
||||
var field = WireFormat.GetTagFieldNumber(tag);
|
||||
switch (WireFormat.GetTagWireType(tag))
|
||||
{
|
||||
case WireFormat.WireType.LengthDelimited:
|
||||
return AddValue(field, new FieldValue(input.ReadBytes()));
|
||||
case WireFormat.WireType.Fixed32:
|
||||
return AddValue(field, new FieldValue(input.ReadFixed32()));
|
||||
case WireFormat.WireType.Fixed64:
|
||||
return AddValue(field, new FieldValue(input.ReadFixed64()));
|
||||
case WireFormat.WireType.Varint:
|
||||
return AddValue(field, new FieldValue(input.ReadRawVarint64()));
|
||||
// For StartGroup, EndGroup or any wire format we don't understand,
|
||||
// just use the normal behavior (call SkipLastField).
|
||||
default:
|
||||
input.SkipLastField();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private CustomOptions AddValue(int field, FieldValue value)
|
||||
{
|
||||
var ret = valuesByField.Count == 0 ? new CustomOptions() : this;
|
||||
List<FieldValue> valuesForField;
|
||||
if (!ret.valuesByField.TryGetValue(field, out valuesForField))
|
||||
{
|
||||
// Expect almost all
|
||||
valuesForField = new List<FieldValue>(1);
|
||||
ret.valuesByField[field] = valuesForField;
|
||||
}
|
||||
valuesForField.Add(value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All field values can be stored as a byte string or a 64-bit integer.
|
||||
/// This struct avoids unnecessary boxing.
|
||||
/// </summary>
|
||||
private struct FieldValue
|
||||
{
|
||||
internal ulong Number { get; }
|
||||
internal ByteString ByteString { get; }
|
||||
|
||||
internal FieldValue(ulong number)
|
||||
{
|
||||
Number = number;
|
||||
ByteString = null;
|
||||
}
|
||||
|
||||
internal FieldValue(ByteString byteString)
|
||||
{
|
||||
Number = 0;
|
||||
ByteString = byteString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2779,6 +2779,8 @@ namespace Google.Protobuf.Reflection {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public FileOptions() {
|
||||
OnConstruction();
|
||||
@ -3299,7 +3301,7 @@ namespace Google.Protobuf.Reflection {
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
input.SkipLastField();
|
||||
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
|
||||
break;
|
||||
case 10: {
|
||||
JavaPackage = input.ReadString();
|
||||
@ -3411,6 +3413,8 @@ namespace Google.Protobuf.Reflection {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public MessageOptions() {
|
||||
OnConstruction();
|
||||
@ -3646,7 +3650,7 @@ namespace Google.Protobuf.Reflection {
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
input.SkipLastField();
|
||||
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
|
||||
break;
|
||||
case 8: {
|
||||
MessageSetWireFormat = input.ReadBool();
|
||||
@ -3689,6 +3693,8 @@ namespace Google.Protobuf.Reflection {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public FieldOptions() {
|
||||
OnConstruction();
|
||||
@ -3980,7 +3986,7 @@ namespace Google.Protobuf.Reflection {
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
input.SkipLastField();
|
||||
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
|
||||
break;
|
||||
case 8: {
|
||||
ctype_ = (global::Google.Protobuf.Reflection.FieldOptions.Types.CType) input.ReadEnum();
|
||||
@ -4062,6 +4068,8 @@ namespace Google.Protobuf.Reflection {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public OneofOptions() {
|
||||
OnConstruction();
|
||||
@ -4147,7 +4155,7 @@ namespace Google.Protobuf.Reflection {
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
input.SkipLastField();
|
||||
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
|
||||
break;
|
||||
case 7994: {
|
||||
uninterpretedOption_.AddEntriesFrom(input, _repeated_uninterpretedOption_codec);
|
||||
@ -4174,6 +4182,8 @@ namespace Google.Protobuf.Reflection {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public EnumOptions() {
|
||||
OnConstruction();
|
||||
@ -4317,7 +4327,7 @@ namespace Google.Protobuf.Reflection {
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
input.SkipLastField();
|
||||
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
|
||||
break;
|
||||
case 16: {
|
||||
AllowAlias = input.ReadBool();
|
||||
@ -4352,6 +4362,8 @@ namespace Google.Protobuf.Reflection {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public EnumValueOptions() {
|
||||
OnConstruction();
|
||||
@ -4467,7 +4479,7 @@ namespace Google.Protobuf.Reflection {
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
input.SkipLastField();
|
||||
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
|
||||
break;
|
||||
case 8: {
|
||||
Deprecated = input.ReadBool();
|
||||
@ -4498,6 +4510,8 @@ namespace Google.Protobuf.Reflection {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public ServiceOptions() {
|
||||
OnConstruction();
|
||||
@ -4613,7 +4627,7 @@ namespace Google.Protobuf.Reflection {
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
input.SkipLastField();
|
||||
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
|
||||
break;
|
||||
case 264: {
|
||||
Deprecated = input.ReadBool();
|
||||
@ -4644,6 +4658,8 @@ namespace Google.Protobuf.Reflection {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public MethodOptions() {
|
||||
OnConstruction();
|
||||
@ -4783,7 +4799,7 @@ namespace Google.Protobuf.Reflection {
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
input.SkipLastField();
|
||||
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
|
||||
break;
|
||||
case 264: {
|
||||
Deprecated = input.ReadBool();
|
||||
|
@ -112,5 +112,10 @@ namespace Google.Protobuf.Reflection
|
||||
{
|
||||
return File.DescriptorPool.FindSymbol<EnumValueDescriptor>(FullName + "." + name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this enum.
|
||||
/// </summary>
|
||||
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
|
||||
}
|
||||
}
|
@ -66,5 +66,10 @@ namespace Google.Protobuf.Reflection
|
||||
/// Returns the enum descriptor that this value is part of.
|
||||
/// </summary>
|
||||
public EnumDescriptor EnumDescriptor { get { return enumDescriptor; } }
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this enum value.
|
||||
/// </summary>
|
||||
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
|
||||
}
|
||||
}
|
@ -250,6 +250,11 @@ namespace Google.Protobuf.Reflection
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this field.
|
||||
/// </summary>
|
||||
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Look up and cross-link all field types etc.
|
||||
/// </summary>
|
||||
|
@ -329,5 +329,10 @@ namespace Google.Protobuf.Reflection
|
||||
/// The file descriptor for <c>descriptor.proto</c>.
|
||||
/// </value>
|
||||
public static FileDescriptor DescriptorProtoFileDescriptor { get { return DescriptorReflection.Descriptor; } }
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this file.
|
||||
/// </summary>
|
||||
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
|
||||
}
|
||||
}
|
||||
|
@ -220,6 +220,11 @@ namespace Google.Protobuf.Reflection
|
||||
public T FindDescriptor<T>(string name) where T : class, IDescriptor =>
|
||||
File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this message.
|
||||
/// </summary>
|
||||
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Looks up and cross-links all fields and nested types.
|
||||
/// </summary>
|
||||
|
@ -67,6 +67,11 @@ namespace Google.Protobuf.Reflection
|
||||
/// </value>
|
||||
public bool IsServerStreaming { get { return proto.ServerStreaming; } }
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this method.
|
||||
/// </summary>
|
||||
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
|
||||
|
||||
internal MethodDescriptor(MethodDescriptorProto proto, FileDescriptor file,
|
||||
ServiceDescriptor parent, int index)
|
||||
: base(file, parent.FullName + "." + proto.Name, index)
|
||||
|
@ -90,6 +90,11 @@ namespace Google.Protobuf.Reflection
|
||||
/// </value>
|
||||
public OneofAccessor Accessor { get { return accessor; } }
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this oneof.
|
||||
/// </summary>
|
||||
public CustomOptions CustomOptions => proto.Options?.CustomOptions ?? CustomOptions.Empty;
|
||||
|
||||
internal void CrossLink()
|
||||
{
|
||||
List<FieldDescriptor> fieldCollection = new List<FieldDescriptor>();
|
||||
|
@ -78,6 +78,11 @@ namespace Google.Protobuf.Reflection
|
||||
return File.DescriptorPool.FindSymbol<MethodDescriptor>(FullName + "." + name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this service.
|
||||
/// </summary>
|
||||
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
|
||||
|
||||
internal void CrossLink()
|
||||
{
|
||||
foreach (MethodDescriptor method in methods)
|
||||
|
@ -120,6 +120,22 @@ inline bool IsDescriptorProto(const FileDescriptor* descriptor) {
|
||||
return descriptor->name() == "google/protobuf/descriptor.proto";
|
||||
}
|
||||
|
||||
// Determines whether the given message is an options message within descriptor.proto.
|
||||
inline bool IsDescriptorOptionMessage(const Descriptor* descriptor) {
|
||||
if (!IsDescriptorProto(descriptor->file())) {
|
||||
return false;
|
||||
}
|
||||
const string name = descriptor->full_name();
|
||||
return name == "google.protobuf.FileOptions" ||
|
||||
name == "google.protobuf.MessageOptions" ||
|
||||
name == "google.protobuf.FieldOptions" ||
|
||||
name == "google.protobuf.OneofOptions" ||
|
||||
name == "google.protobuf.EnumOptions" ||
|
||||
name == "google.protobuf.EnumValueOptions" ||
|
||||
name == "google.protobuf.ServiceOptions" ||
|
||||
name == "google.protobuf.MethodOptions";
|
||||
}
|
||||
|
||||
inline bool IsWrapperType(const FieldDescriptor* descriptor) {
|
||||
return descriptor->type() == FieldDescriptor::TYPE_MESSAGE &&
|
||||
descriptor->message_type()->file()->name() == "google/protobuf/wrappers.proto";
|
||||
|
@ -151,6 +151,12 @@ void MessageGenerator::Generate(io::Printer* printer) {
|
||||
" get { return Descriptor; }\n"
|
||||
"}\n"
|
||||
"\n");
|
||||
// CustomOptions property, only for options messages
|
||||
if (IsDescriptorOptionMessage(descriptor_)) {
|
||||
printer->Print(
|
||||
"internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;\n"
|
||||
"\n");
|
||||
}
|
||||
|
||||
// Parameterless constructor and partial OnConstruction method.
|
||||
WriteGeneratedCodeAttributes(printer);
|
||||
@ -475,10 +481,18 @@ void MessageGenerator::GenerateMergingMethods(io::Printer* printer) {
|
||||
" switch(tag) {\n");
|
||||
printer->Indent();
|
||||
printer->Indent();
|
||||
printer->Print(
|
||||
"default:\n"
|
||||
" input.SkipLastField();\n" // We're not storing the data, but we still need to consume it.
|
||||
" break;\n");
|
||||
// Option messages need to store unknown fields so that options can be parsed later.
|
||||
if (IsDescriptorOptionMessage(descriptor_)) {
|
||||
printer->Print(
|
||||
"default:\n"
|
||||
" CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);\n"
|
||||
" break;\n");
|
||||
} else {
|
||||
printer->Print(
|
||||
"default:\n"
|
||||
" input.SkipLastField();\n" // We're not storing the data, but we still need to consume it.
|
||||
" break;\n");
|
||||
}
|
||||
for (int i = 0; i < fields_by_number().size(); i++) {
|
||||
const FieldDescriptor* field = fields_by_number()[i];
|
||||
internal::WireFormatLite::WireType wt =
|
||||
|
Loading…
Reference in New Issue
Block a user