9
9
10
10
using System ;
11
11
using System . Collections . Concurrent ;
12
+ using System . Collections . Generic ;
13
+ using System . Linq ;
12
14
using static Google . Protobuf . Reflection . FeatureSet . Types ;
13
15
14
16
namespace Google . Protobuf . Reflection ;
@@ -22,52 +24,55 @@ namespace Google.Protobuf.Reflection;
22
24
/// If either of those features are ever implemented in this runtime,
23
25
/// the feature settings will be exposed as properties in this class.
24
26
/// </remarks>
25
- internal sealed class FeatureSetDescriptor
27
+ internal sealed partial class FeatureSetDescriptor
26
28
{
27
29
private static readonly ConcurrentDictionary < FeatureSet , FeatureSetDescriptor > cache = new ( ) ;
28
30
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
31
+ private static readonly IReadOnlyDictionary < Edition , FeatureSetDescriptor > descriptorsByEdition = BuildEditionDefaults ( ) ;
32
+
33
+ // Note: if the debugger is set to break within this code, various type initializers will fail
34
+ // as the debugger will try to call ToString() on messages, requiring descriptors to be accessed etc.
35
+ // There's a possible workaround of using a hard-coded bootstrapping FeatureSetDescriptor to be returned
36
+ // by GetEditionDefaults if descriptorsByEdition is null, but it's ugly and likely just pushes the problem
37
+ // elsewhere. Normal debugging sessions (where the initial bootstrapping code doesn't hit any breakpoints)
38
+ // do not cause any problems.
39
+ private static IReadOnlyDictionary < Edition , FeatureSetDescriptor > BuildEditionDefaults ( )
40
+ {
41
+ var featureSetDefaults = FeatureSetDefaults . Parser . ParseFrom ( Convert . FromBase64String ( DefaultsBase64 ) ) ;
42
+ var ret = new Dictionary < Edition , FeatureSetDescriptor > ( ) ;
43
+
44
+ // Note: Enum.GetValues<TEnum> isn't available until .NET 5. It's not worth making this conditional
45
+ // based on that.
46
+ var supportedEditions = ( ( Edition [ ] ) Enum . GetValues ( typeof ( Edition ) ) )
47
+ . OrderBy ( x => x )
48
+ . Where ( e => e >= featureSetDefaults . MinimumEdition && e <= featureSetDefaults . MaximumEdition ) ;
49
+
50
+ // We assume the embedded defaults will always contain "legacy".
51
+ var currentDescriptor = MaybeCreateDescriptor ( Edition . Legacy ) ;
52
+ foreach ( var edition in supportedEditions )
44
53
{
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
+ currentDescriptor = MaybeCreateDescriptor ( edition ) ?? currentDescriptor ;
55
+ ret [ edition ] = currentDescriptor ;
56
+ }
57
+ return ret ;
58
+
59
+ FeatureSetDescriptor MaybeCreateDescriptor ( Edition edition )
54
60
{
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
- } ) ;
61
+ var editionDefaults = featureSetDefaults . Defaults . SingleOrDefault ( d => d . Edition == edition ) ;
62
+ if ( editionDefaults is null )
63
+ {
64
+ return null ;
65
+ }
66
+ var proto = new FeatureSet ( ) ;
67
+ proto . MergeFrom ( editionDefaults . FixedFeatures ) ;
68
+ proto . MergeFrom ( editionDefaults . OverridableFeatures ) ;
69
+ return new FeatureSetDescriptor ( proto ) ;
70
+ }
71
+ }
62
72
63
73
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
- } ;
74
+ descriptorsByEdition . TryGetValue ( edition , out var defaults ) ? defaults
75
+ : throw new ArgumentOutOfRangeException ( $ "Unsupported edition: { edition } ") ;
71
76
72
77
// Visible for testing. The underlying feature set proto, usually derived during
73
78
// feature resolution.
0 commit comments