2024-03-16 11:34:55 -04:00
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package protodesc
import (
"fmt"
"os"
"sync"
"google.golang.org/protobuf/internal/editiondefaults"
"google.golang.org/protobuf/internal/filedesc"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/descriptorpb"
gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
)
var defaults = & descriptorpb . FeatureSetDefaults { }
var defaultsCacheMu sync . Mutex
var defaultsCache = make ( map [ filedesc . Edition ] * descriptorpb . FeatureSet )
func init ( ) {
err := proto . Unmarshal ( editiondefaults . Defaults , defaults )
if err != nil {
fmt . Fprintf ( os . Stderr , "unmarshal editions defaults: %v\n" , err )
os . Exit ( 1 )
}
}
func fromEditionProto ( epb descriptorpb . Edition ) filedesc . Edition {
return filedesc . Edition ( epb )
}
func toEditionProto ( ed filedesc . Edition ) descriptorpb . Edition {
switch ed {
case filedesc . EditionUnknown :
return descriptorpb . Edition_EDITION_UNKNOWN
case filedesc . EditionProto2 :
return descriptorpb . Edition_EDITION_PROTO2
case filedesc . EditionProto3 :
return descriptorpb . Edition_EDITION_PROTO3
case filedesc . Edition2023 :
return descriptorpb . Edition_EDITION_2023
default :
panic ( fmt . Sprintf ( "unknown value for edition: %v" , ed ) )
}
}
func getFeatureSetFor ( ed filedesc . Edition ) * descriptorpb . FeatureSet {
defaultsCacheMu . Lock ( )
defer defaultsCacheMu . Unlock ( )
if def , ok := defaultsCache [ ed ] ; ok {
return def
}
edpb := toEditionProto ( ed )
if defaults . GetMinimumEdition ( ) > edpb || defaults . GetMaximumEdition ( ) < edpb {
// This should never happen protodesc.(FileOptions).New would fail when
// initializing the file descriptor.
// This most likely means the embedded defaults were not updated.
fmt . Fprintf ( os . Stderr , "internal error: unsupported edition %v (did you forget to update the embedded defaults (i.e. the bootstrap descriptor proto)?)\n" , edpb )
os . Exit ( 1 )
}
2024-10-12 15:49:59 -04:00
fsed := defaults . GetDefaults ( ) [ 0 ]
2024-03-16 11:34:55 -04:00
// Using a linear search for now.
// Editions are guaranteed to be sorted and thus we could use a binary search.
// Given that there are only a handful of editions (with one more per year)
// there is not much reason to use a binary search.
for _ , def := range defaults . GetDefaults ( ) {
if def . GetEdition ( ) <= edpb {
2024-10-12 15:49:59 -04:00
fsed = def
2024-03-16 11:34:55 -04:00
} else {
break
}
}
2024-10-12 15:49:59 -04:00
fs := proto . Clone ( fsed . GetFixedFeatures ( ) ) . ( * descriptorpb . FeatureSet )
proto . Merge ( fs , fsed . GetOverridableFeatures ( ) )
2024-03-16 11:34:55 -04:00
defaultsCache [ ed ] = fs
return fs
}
// mergeEditionFeatures merges the parent and child feature sets. This function
// should be used when initializing Go descriptors from descriptor protos which
// is why the parent is a filedesc.EditionsFeatures (Go representation) while
// the child is a descriptorproto.FeatureSet (protoc representation).
// Any feature set by the child overwrites what is set by the parent.
func mergeEditionFeatures ( parentDesc protoreflect . Descriptor , child * descriptorpb . FeatureSet ) filedesc . EditionFeatures {
var parentFS filedesc . EditionFeatures
switch p := parentDesc . ( type ) {
case * filedesc . File :
parentFS = p . L1 . EditionFeatures
case * filedesc . Message :
parentFS = p . L1 . EditionFeatures
default :
panic ( fmt . Sprintf ( "unknown parent type %T" , parentDesc ) )
}
if child == nil {
return parentFS
}
if fp := child . FieldPresence ; fp != nil {
parentFS . IsFieldPresence = * fp == descriptorpb . FeatureSet_LEGACY_REQUIRED ||
* fp == descriptorpb . FeatureSet_EXPLICIT
parentFS . IsLegacyRequired = * fp == descriptorpb . FeatureSet_LEGACY_REQUIRED
}
if et := child . EnumType ; et != nil {
parentFS . IsOpenEnum = * et == descriptorpb . FeatureSet_OPEN
}
if rfe := child . RepeatedFieldEncoding ; rfe != nil {
parentFS . IsPacked = * rfe == descriptorpb . FeatureSet_PACKED
}
if utf8val := child . Utf8Validation ; utf8val != nil {
parentFS . IsUTF8Validated = * utf8val == descriptorpb . FeatureSet_VERIFY
}
if me := child . MessageEncoding ; me != nil {
parentFS . IsDelimitedEncoded = * me == descriptorpb . FeatureSet_DELIMITED
}
if jf := child . JsonFormat ; jf != nil {
parentFS . IsJSONCompliant = * jf == descriptorpb . FeatureSet_ALLOW
}
if goFeatures , ok := proto . GetExtension ( child , gofeaturespb . E_Go ) . ( * gofeaturespb . GoFeatures ) ; ok && goFeatures != nil {
if luje := goFeatures . LegacyUnmarshalJsonEnum ; luje != nil {
parentFS . GenerateLegacyUnmarshalJSON = * luje
}
}
return parentFS
}
// initFileDescFromFeatureSet initializes editions related fields in fd based
// on fs. If fs is nil it is assumed to be an empty featureset and all fields
// will be initialized with the appropriate default. fd.L1.Edition must be set
// before calling this function.
func initFileDescFromFeatureSet ( fd * filedesc . File , fs * descriptorpb . FeatureSet ) {
dfs := getFeatureSetFor ( fd . L1 . Edition )
// initialize the featureset with the defaults
fd . L1 . EditionFeatures = mergeEditionFeatures ( fd , dfs )
// overwrite any options explicitly specified
fd . L1 . EditionFeatures = mergeEditionFeatures ( fd , fs )
}