parent
119e71c40a
commit
450022de99
16 changed files with 809 additions and 67 deletions
@ -0,0 +1,373 @@ |
|||||||
|
#region Copyright notice and license |
||||||
|
// Protocol Buffers - Google's data interchange format |
||||||
|
// Copyright 2008 Google Inc. All rights reserved. |
||||||
|
// |
||||||
|
// Use of this source code is governed by a BSD-style |
||||||
|
// license that can be found in the LICENSE file or at |
||||||
|
// https://developers.google.com/open-source/licenses/bsd |
||||||
|
#endregion |
||||||
|
|
||||||
|
using Google.Protobuf.Reflection; |
||||||
|
using NUnit.Framework; |
||||||
|
using Pb; |
||||||
|
using static Google.Protobuf.Reflection.FieldDescriptorProto.Types; |
||||||
|
using Type = Google.Protobuf.Reflection.FieldDescriptorProto.Types.Type; |
||||||
|
|
||||||
|
namespace Google.Protobuf.Test.Reflection; |
||||||
|
|
||||||
|
public class FeatureInheritanceTest |
||||||
|
{ |
||||||
|
// Note: there's no test for file defaults, as we don't have the same access to modify the |
||||||
|
// global defaults in C# that exists in Java. |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void FileOverrides() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto, 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void FileMessageInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto, 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void FileMessageOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto, 3); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void FileEnumInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto, 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.EnumTypes[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void FileEnumOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto, 3); |
||||||
|
SetTestFeature(fileProto.EnumType[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.EnumTypes[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void FileExtensionInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto, 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.EnumTypes[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void FileExtensionOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto, 3); |
||||||
|
SetTestFeature(fileProto.Extension[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.Extensions.UnorderedExtensions[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void FileServiceInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto, 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.Services[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void FileServiceOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto, 3); |
||||||
|
SetTestFeature(fileProto.Service[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.Services[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MessageFieldInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].Fields.InFieldNumberOrder()[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MessageFieldOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
SetTestFeature(fileProto.MessageType[0].Field[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].Fields.InFieldNumberOrder()[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MessageEnumInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].EnumTypes[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MessageEnumOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
SetTestFeature(fileProto.MessageType[0].EnumType[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].EnumTypes[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MessageMessageInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].NestedTypes[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MessageMessageOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
SetTestFeature(fileProto.MessageType[0].NestedType[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].NestedTypes[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MessageExtensionInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].Extensions.UnorderedExtensions[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MessageExtensionOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
SetTestFeature(fileProto.MessageType[0].Extension[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].Extensions.UnorderedExtensions[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MessageOneofInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].Oneofs[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MessageOneofOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
SetTestFeature(fileProto.MessageType[0].OneofDecl[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].Oneofs[0].Fields[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void OneofFieldInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].Oneofs[0].Fields[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void OneofFieldOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
SetTestFeature(fileProto.MessageType[0].OneofDecl[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].Oneofs[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void EnumValueInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.EnumType[0], 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.EnumTypes[0].Values[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void EnumValueOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.MessageType[0], 3); |
||||||
|
SetTestFeature(fileProto.EnumType[0].Value[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.EnumTypes[0].Values[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void ServiceMethodInherit() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.Service[0], 3); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(3, GetTestFeature(fileDescriptor.Services[0].Methods[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void ServiceMethodOverride() |
||||||
|
{ |
||||||
|
var fileProto = CreateFileProto(); |
||||||
|
SetTestFeature(fileProto.Service[0], 3); |
||||||
|
SetTestFeature(fileProto.Service[0].Method[0], 5); |
||||||
|
var fileDescriptor = Build(fileProto); |
||||||
|
Assert.AreEqual(5, GetTestFeature(fileDescriptor.Services[0].Methods[0].Features)); |
||||||
|
} |
||||||
|
|
||||||
|
private static int GetTestFeature(FeatureSetDescriptor features) => |
||||||
|
(features.Proto.GetExtension(UnittestFeaturesExtensions.Test) ?? new TestFeatures()).IntMultipleFeature; |
||||||
|
|
||||||
|
private static void SetTestFeature(FileDescriptorProto proto, int value) |
||||||
|
{ |
||||||
|
proto.Options ??= new FileOptions(); |
||||||
|
proto.Options.Features ??= new FeatureSet(); |
||||||
|
SetTestFeature(proto.Options.Features, value); |
||||||
|
} |
||||||
|
|
||||||
|
private static void SetTestFeature(DescriptorProto proto, int value) |
||||||
|
{ |
||||||
|
proto.Options ??= new MessageOptions(); |
||||||
|
proto.Options.Features ??= new FeatureSet(); |
||||||
|
SetTestFeature(proto.Options.Features, value); |
||||||
|
} |
||||||
|
|
||||||
|
private static void SetTestFeature(EnumDescriptorProto proto, int value) |
||||||
|
{ |
||||||
|
proto.Options ??= new EnumOptions(); |
||||||
|
proto.Options.Features ??= new FeatureSet(); |
||||||
|
SetTestFeature(proto.Options.Features, value); |
||||||
|
} |
||||||
|
|
||||||
|
private static void SetTestFeature(EnumValueDescriptorProto proto, int value) |
||||||
|
{ |
||||||
|
proto.Options ??= new EnumValueOptions(); |
||||||
|
proto.Options.Features ??= new FeatureSet(); |
||||||
|
SetTestFeature(proto.Options.Features, value); |
||||||
|
} |
||||||
|
|
||||||
|
private static void SetTestFeature(FieldDescriptorProto proto, int value) |
||||||
|
{ |
||||||
|
proto.Options ??= new FieldOptions(); |
||||||
|
proto.Options.Features ??= new FeatureSet(); |
||||||
|
SetTestFeature(proto.Options.Features, value); |
||||||
|
} |
||||||
|
|
||||||
|
private static void SetTestFeature(ServiceDescriptorProto proto, int value) |
||||||
|
{ |
||||||
|
proto.Options ??= new ServiceOptions(); |
||||||
|
proto.Options.Features ??= new FeatureSet(); |
||||||
|
SetTestFeature(proto.Options.Features, value); |
||||||
|
} |
||||||
|
|
||||||
|
private static void SetTestFeature(OneofDescriptorProto proto, int value) |
||||||
|
{ |
||||||
|
proto.Options ??= new OneofOptions(); |
||||||
|
proto.Options.Features ??= new FeatureSet(); |
||||||
|
SetTestFeature(proto.Options.Features, value); |
||||||
|
} |
||||||
|
|
||||||
|
private static void SetTestFeature(MethodDescriptorProto proto, int value) |
||||||
|
{ |
||||||
|
proto.Options ??= new MethodOptions(); |
||||||
|
proto.Options.Features ??= new FeatureSet(); |
||||||
|
SetTestFeature(proto.Options.Features, value); |
||||||
|
} |
||||||
|
|
||||||
|
private static void SetTestFeature(FeatureSet features, int value) => |
||||||
|
features.SetExtension(UnittestFeaturesExtensions.Test, new TestFeatures { IntMultipleFeature = value }); |
||||||
|
|
||||||
|
private static FileDescriptor Build(FileDescriptorProto fileProto) => |
||||||
|
FileDescriptor.BuildFromByteStrings(new[] { fileProto.ToByteString() }, new ExtensionRegistry { UnittestFeaturesExtensions.Test })[0]; |
||||||
|
|
||||||
|
private static FileDescriptorProto CreateFileProto() => new FileDescriptorProto |
||||||
|
{ |
||||||
|
Name = "some/filename/some.proto", |
||||||
|
Package = "proto2_unittest", |
||||||
|
Edition = Edition._2023, |
||||||
|
Syntax = "editions", |
||||||
|
Extension = |
||||||
|
{ |
||||||
|
new FieldDescriptorProto { Name = "top_extension", Number = 10, Type = Type.Int32, Label = Label.Optional, Extendee = ".proto2_unittest.TopMessage" } |
||||||
|
}, |
||||||
|
EnumType = |
||||||
|
{ |
||||||
|
new EnumDescriptorProto { Name = "TopEnum", Value = { new EnumValueDescriptorProto { Name = "TOP_VALUE", Number = 0 } } } |
||||||
|
}, |
||||||
|
MessageType = |
||||||
|
{ |
||||||
|
new DescriptorProto |
||||||
|
{ |
||||||
|
Name = "TopMessage", |
||||||
|
Field = |
||||||
|
{ |
||||||
|
new FieldDescriptorProto { Name = "field", Number = 1, Type = Type.Int32, Label = Label.Optional }, |
||||||
|
new FieldDescriptorProto { Name = "oneof_field", Number = 2, Type = Type.Int32, Label = Label.Optional, OneofIndex = 0 } |
||||||
|
}, |
||||||
|
Extension = |
||||||
|
{ |
||||||
|
new FieldDescriptorProto { Name = "nested_extension", Number = 11, Type = Type.Int32, Label = Label.Optional, Extendee = ".proto2_unittest.TopMessage" } |
||||||
|
}, |
||||||
|
NestedType = |
||||||
|
{ |
||||||
|
new DescriptorProto { Name = "NestedMessage" }, |
||||||
|
}, |
||||||
|
EnumType = |
||||||
|
{ |
||||||
|
new EnumDescriptorProto { Name = "NestedEnum", Value = { new EnumValueDescriptorProto { Name = "NESTED_VALUE", Number = 0 } } } |
||||||
|
}, |
||||||
|
OneofDecl = { new OneofDescriptorProto { Name = "Oneof" } } |
||||||
|
} |
||||||
|
}, |
||||||
|
Service = |
||||||
|
{ |
||||||
|
new ServiceDescriptorProto |
||||||
|
{ |
||||||
|
Name = "TestService", |
||||||
|
Method = { new MethodDescriptorProto { Name = "CallMethod", InputType = ".proto2_unittest.TopMessage", OutputType = ".proto2_unittest.TopMessage" } } |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
#region Copyright notice and license |
||||||
|
// Protocol Buffers - Google's data interchange format |
||||||
|
// Copyright 2008 Google Inc. All rights reserved. |
||||||
|
// |
||||||
|
// Use of this source code is governed by a BSD-style |
||||||
|
// license that can be found in the LICENSE file or at |
||||||
|
// https://developers.google.com/open-source/licenses/bsd |
||||||
|
#endregion |
||||||
|
|
||||||
|
using Google.Protobuf.Reflection; |
||||||
|
using NUnit.Framework; |
||||||
|
using System; |
||||||
|
using System.Linq; |
||||||
|
|
||||||
|
namespace Google.Protobuf.Test.Reflection; |
||||||
|
|
||||||
|
public class FeatureSetDescriptorTest |
||||||
|
{ |
||||||
|
// Canonical serialized form of the edition defaults, generated by embed_edition_defaults. |
||||||
|
// TODO: Update this automatically. |
||||||
|
private const string DefaultsBase64 = "CrEBEqsBCAEQAhgCIAMoATAC6vAEMQj+//////////8BEAEYASABKAEwATgBQAFIAVABWABlzcyMP2oAcAB4AYIBBDIwMjPy8AQxCP7//////////wEQARgBIAEoATABOAFAAUgBUAFYAGXNzIw/agBwAHgBggEEMjAyM/rwBDEI/v//////////ARABGAEgASgBMAE4AUABSAFQAVgAZc3MjD9qAHAAeAGCAQQyMDIzGOYHCrEBEqsBCAIQARgBIAIoATAB6vAEMQj9//////////8BEAEYASABKAEwATgBQAFIAVABWABlzcyMP2oAcAB4AYIBBDIwMjPy8AQxCP3//////////wEQARgBIAEoATABOAFAAUgBUAFYAGXNzIw/agBwAHgBggEEMjAyM/rwBDEI/f//////////ARABGAEgASgBMAE4AUABSAFQAVgAZc3MjD9qAHAAeAGCAQQyMDIzGOcHCsMBEr0BCAEQARgBIAIoATAB6vAENwgBEAEYASABKAEwATgBQAFIAVABWABlzcyMP2oPCAEQAR0AAMA/IgQyMDIzcAF4AYIBBDIwMjPy8AQ3CAEQARgBIAEoATABOAFAAUgBUAFYAGXNzIw/ag8IARABHQAAwD8iBDIwMjNwAXgBggEEMjAyM/rwBDcIARABGAEgASgBMAE4AUABSAFQAVgAZc3MjD9qDwgBEAEdAADAPyIEMjAyM3ABeAGCAQQyMDIzGOgHIOgHKOgH"; |
||||||
|
|
||||||
|
[Test] |
||||||
|
[TestCase(Edition.Proto2)] |
||||||
|
[TestCase(Edition.Proto3)] |
||||||
|
[TestCase(Edition._2023)] |
||||||
|
public void DefaultsMatchCanonicalSerializedForm(Edition edition) |
||||||
|
{ |
||||||
|
var canonicalDefaults = FeatureSetDefaults.Parser |
||||||
|
.WithDiscardUnknownFields(true) // Discard language-specific extensions. |
||||||
|
.ParseFrom(Convert.FromBase64String(DefaultsBase64)); |
||||||
|
var canonicalEditionDefaults = canonicalDefaults.Defaults.Single(def => def.Edition == edition).Features; |
||||||
|
var candidateEditionDefaults = FeatureSetDescriptor.GetEditionDefaults(edition).Proto; |
||||||
|
|
||||||
|
Assert.AreEqual(canonicalEditionDefaults, candidateEditionDefaults); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,118 @@ |
|||||||
|
#region Copyright notice and license |
||||||
|
// Protocol Buffers - Google's data interchange format |
||||||
|
// Copyright 2008 Google Inc. All rights reserved. |
||||||
|
// |
||||||
|
// Use of this source code is governed by a BSD-style |
||||||
|
// license that can be found in the LICENSE file or at |
||||||
|
// https://developers.google.com/open-source/licenses/bsd |
||||||
|
#endregion |
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Concurrent; |
||||||
|
using static Google.Protobuf.Reflection.FeatureSet.Types; |
||||||
|
|
||||||
|
namespace Google.Protobuf.Reflection; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// A resolved set of features for a file, message etc. |
||||||
|
/// </summary> |
||||||
|
/// <remarks> |
||||||
|
/// Only features supported by the C# runtime are exposed; currently |
||||||
|
/// all enums in C# are open, and we never perform UTF-8 validation. |
||||||
|
/// If either of those features are ever implemented in this runtime, |
||||||
|
/// the feature settings will be exposed as properties in this class. |
||||||
|
/// </remarks> |
||||||
|
internal sealed class FeatureSetDescriptor |
||||||
|
{ |
||||||
|
private static readonly ConcurrentDictionary<FeatureSet, FeatureSetDescriptor> cache = new(); |
||||||
|
|
||||||
|
// Note: this approach is deliberately chosen to circumvent bootstrapping issues. |
||||||
|
// This can still be tested using the binary representation. |
||||||
|
// TODO: Generate this code (as a partial class) from the binary representation. |
||||||
|
private static readonly FeatureSetDescriptor edition2023Defaults = new FeatureSetDescriptor( |
||||||
|
new FeatureSet |
||||||
|
{ |
||||||
|
EnumType = EnumType.Open, |
||||||
|
FieldPresence = FieldPresence.Explicit, |
||||||
|
JsonFormat = JsonFormat.Allow, |
||||||
|
MessageEncoding = MessageEncoding.LengthPrefixed, |
||||||
|
RepeatedFieldEncoding = RepeatedFieldEncoding.Packed, |
||||||
|
Utf8Validation = Utf8Validation.Verify, |
||||||
|
}); |
||||||
|
private static readonly FeatureSetDescriptor proto2Defaults = new FeatureSetDescriptor( |
||||||
|
new FeatureSet |
||||||
|
{ |
||||||
|
EnumType = EnumType.Closed, |
||||||
|
FieldPresence = FieldPresence.Explicit, |
||||||
|
JsonFormat = JsonFormat.LegacyBestEffort, |
||||||
|
MessageEncoding = MessageEncoding.LengthPrefixed, |
||||||
|
RepeatedFieldEncoding = RepeatedFieldEncoding.Expanded, |
||||||
|
Utf8Validation = Utf8Validation.None, |
||||||
|
}); |
||||||
|
private static readonly FeatureSetDescriptor proto3Defaults = new FeatureSetDescriptor( |
||||||
|
new FeatureSet |
||||||
|
{ |
||||||
|
EnumType = EnumType.Open, |
||||||
|
FieldPresence = FieldPresence.Implicit, |
||||||
|
JsonFormat = JsonFormat.Allow, |
||||||
|
MessageEncoding = MessageEncoding.LengthPrefixed, |
||||||
|
RepeatedFieldEncoding = RepeatedFieldEncoding.Packed, |
||||||
|
Utf8Validation = Utf8Validation.Verify, |
||||||
|
}); |
||||||
|
|
||||||
|
internal static FeatureSetDescriptor GetEditionDefaults(Edition edition) => |
||||||
|
edition switch |
||||||
|
{ |
||||||
|
Edition.Proto2 => proto2Defaults, |
||||||
|
Edition.Proto3 => proto3Defaults, |
||||||
|
Edition._2023 => edition2023Defaults, |
||||||
|
_ => throw new ArgumentOutOfRangeException($"Unsupported edition: {edition}") |
||||||
|
}; |
||||||
|
|
||||||
|
// Visible for testing. The underlying feature set proto, usually derived during |
||||||
|
// feature resolution. |
||||||
|
internal FeatureSet Proto { get; } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Only relevant to fields. Indicates if a field has explicit presence. |
||||||
|
/// </summary> |
||||||
|
internal FieldPresence FieldPresence => Proto.FieldPresence; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Only relevant to fields. Indicates how a repeated field should be encoded. |
||||||
|
/// </summary> |
||||||
|
internal RepeatedFieldEncoding RepeatedFieldEncoding => Proto.RepeatedFieldEncoding; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Only relevant to fields. Indicates how a message-valued field should be encoded. |
||||||
|
/// </summary> |
||||||
|
internal MessageEncoding MessageEncoding => Proto.MessageEncoding; |
||||||
|
|
||||||
|
private FeatureSetDescriptor(FeatureSet proto) |
||||||
|
{ |
||||||
|
Proto = proto; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Returns a new descriptor based on this one, with the specified overrides. |
||||||
|
/// Multiple calls to this method that produce equivalent feature sets will return |
||||||
|
/// the same instance. |
||||||
|
/// </summary> |
||||||
|
/// <param name="overrides">The proto representation of the "child" feature set to merge with this |
||||||
|
/// one. May be null, in which case this descriptor is returned.</param> |
||||||
|
/// <returns>A descriptor based on the current one, with the given set of overrides.</returns> |
||||||
|
public FeatureSetDescriptor MergedWith(FeatureSet overrides) |
||||||
|
{ |
||||||
|
if (overrides is null) |
||||||
|
{ |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
// Note: It would be nice if we could avoid cloning unless |
||||||
|
// there are actual changes, but this won't happen that often; |
||||||
|
// it'll be temporary garbage. |
||||||
|
var clone = Proto.Clone(); |
||||||
|
clone.MergeFrom(overrides); |
||||||
|
return cache.GetOrAdd(clone, clone => new FeatureSetDescriptor(clone)); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue