#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; /// /// A resolved set of features for a file, message etc. /// /// /// 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. /// internal sealed class FeatureSetDescriptor { private static readonly ConcurrentDictionary 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; } /// /// Only relevant to fields. Indicates if a field has explicit presence. /// internal FieldPresence FieldPresence => Proto.FieldPresence; /// /// Only relevant to fields. Indicates how a repeated field should be encoded. /// internal RepeatedFieldEncoding RepeatedFieldEncoding => Proto.RepeatedFieldEncoding; /// /// Only relevant to fields. Indicates how a message-valued field should be encoded. /// internal MessageEncoding MessageEncoding => Proto.MessageEncoding; private FeatureSetDescriptor(FeatureSet proto) { Proto = proto; } /// /// 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. /// /// The proto representation of the "child" feature set to merge with this /// one. May be null, in which case this descriptor is returned. /// A descriptor based on the current one, with the given set of overrides. 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)); } }