1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using System; 11 using System.Collections.Concurrent; 12 using static Google.Protobuf.Reflection.FeatureSet.Types; 13 14 namespace Google.Protobuf.Reflection; 15 16 /// <summary> 17 /// A resolved set of features for a file, message etc. 18 /// </summary> 19 /// <remarks> 20 /// Only features supported by the C# runtime are exposed; currently 21 /// all enums in C# are open, and we never perform UTF-8 validation. 22 /// If either of those features are ever implemented in this runtime, 23 /// the feature settings will be exposed as properties in this class. 24 /// </remarks> 25 internal sealed class FeatureSetDescriptor 26 { 27 private static readonly ConcurrentDictionary<FeatureSet, FeatureSetDescriptor> cache = new(); 28 29 // Note: this approach is deliberately chosen to circumvent bootstrapping issues. 30 // This can still be tested using the binary representation. 31 // TODO: Generate this code (as a partial class) from the binary representation. 32 private static readonly FeatureSetDescriptor edition2023Defaults = new FeatureSetDescriptor( 33 new FeatureSet 34 { 35 EnumType = EnumType.Open, 36 FieldPresence = FieldPresence.Explicit, 37 JsonFormat = JsonFormat.Allow, 38 MessageEncoding = MessageEncoding.LengthPrefixed, 39 RepeatedFieldEncoding = RepeatedFieldEncoding.Packed, 40 Utf8Validation = Utf8Validation.Verify, 41 }); 42 private static readonly FeatureSetDescriptor proto2Defaults = new FeatureSetDescriptor( 43 new FeatureSet 44 { 45 EnumType = EnumType.Closed, 46 FieldPresence = FieldPresence.Explicit, 47 JsonFormat = JsonFormat.LegacyBestEffort, 48 MessageEncoding = MessageEncoding.LengthPrefixed, 49 RepeatedFieldEncoding = RepeatedFieldEncoding.Expanded, 50 Utf8Validation = Utf8Validation.None, 51 }); 52 private static readonly FeatureSetDescriptor proto3Defaults = new FeatureSetDescriptor( 53 new FeatureSet 54 { 55 EnumType = EnumType.Open, 56 FieldPresence = FieldPresence.Implicit, 57 JsonFormat = JsonFormat.Allow, 58 MessageEncoding = MessageEncoding.LengthPrefixed, 59 RepeatedFieldEncoding = RepeatedFieldEncoding.Packed, 60 Utf8Validation = Utf8Validation.Verify, 61 }); 62 63 internal static FeatureSetDescriptor GetEditionDefaults(Edition edition) => 64 edition switch 65 { 66 Edition.Proto2 => proto2Defaults, 67 Edition.Proto3 => proto3Defaults, 68 Edition._2023 => edition2023Defaults, 69 _ => throw new ArgumentOutOfRangeException($"Unsupported edition: {edition}") 70 }; 71 72 // Visible for testing. The underlying feature set proto, usually derived during 73 // feature resolution. 74 internal FeatureSet Proto { get; } 75 76 /// <summary> 77 /// Only relevant to fields. Indicates if a field has explicit presence. 78 /// </summary> 79 internal FieldPresence FieldPresence => Proto.FieldPresence; 80 81 /// <summary> 82 /// Only relevant to fields. Indicates how a repeated field should be encoded. 83 /// </summary> 84 internal RepeatedFieldEncoding RepeatedFieldEncoding => Proto.RepeatedFieldEncoding; 85 86 /// <summary> 87 /// Only relevant to fields. Indicates how a message-valued field should be encoded. 88 /// </summary> 89 internal MessageEncoding MessageEncoding => Proto.MessageEncoding; 90 FeatureSetDescriptor(FeatureSet proto)91 private FeatureSetDescriptor(FeatureSet proto) 92 { 93 Proto = proto; 94 } 95 96 /// <summary> 97 /// Returns a new descriptor based on this one, with the specified overrides. 98 /// Multiple calls to this method that produce equivalent feature sets will return 99 /// the same instance. 100 /// </summary> 101 /// <param name="overrides">The proto representation of the "child" feature set to merge with this 102 /// one. May be null, in which case this descriptor is returned.</param> 103 /// <returns>A descriptor based on the current one, with the given set of overrides.</returns> MergedWith(FeatureSet overrides)104 public FeatureSetDescriptor MergedWith(FeatureSet overrides) 105 { 106 if (overrides is null) 107 { 108 return this; 109 } 110 111 // Note: It would be nice if we could avoid cloning unless 112 // there are actual changes, but this won't happen that often; 113 // it'll be temporary garbage. 114 var clone = Proto.Clone(); 115 clone.MergeFrom(overrides); 116 return cache.GetOrAdd(clone, clone => new FeatureSetDescriptor(clone)); 117 } 118 } 119