Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions private/bufpkg/bufcheck/breaking_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,18 @@ func TestRunBreakingFieldSameJavaUTF8Validation(t *testing.T) {
)
}

func TestRunBreakingFieldSameGoStripEnumPrefix(t *testing.T) {
t.Skip("TODO: enable when edition 2024 is supported")
t.Parallel()
testBreaking(
t,
"breaking_field_same_go_strip_enum_prefix",
bufanalysistesting.NewFileAnnotation(t, "1.proto", 8, 1, 8, 7, "FIELD_SAME_GO_STRIP_ENUM_PREFIX"),
bufanalysistesting.NewFileAnnotation(t, "1.proto", 15, 1, 15, 12, "FIELD_SAME_GO_STRIP_ENUM_PREFIX"),
bufanalysistesting.NewFileAnnotation(t, "1.proto", 22, 1, 22, 17, "FIELD_SAME_GO_STRIP_ENUM_PREFIX"),
)
}

func TestRunBreakingFieldSameDefault(t *testing.T) {
t.Parallel()
testBreaking(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ var (
Type: check.RuleTypeBreaking,
Handler: bufcheckserverhandle.HandleBreakingFieldSameJavaUTF8Validation,
}
// BreakingFieldSameGoStripEnumPrefixRuleSpecBuilder is a rule spec builder.
BreakingFieldSameGoStripEnumPrefixRuleSpecBuilder = &bufcheckserverutil.RuleSpecBuilder{
ID: "FIELD_SAME_GO_STRIP_ENUM_PREFIX",
Purpose: "Checks that enums have the same Go strip enum prefix, based on (pb.go).strip_enum_prefix feature.",
Type: check.RuleTypeBreaking,
Handler: bufcheckserverhandle.HandleBreakingFieldSameGoStripEnumPrefix,
}
// BreakingFieldSameDefaultRuleSpecBuilder is a rule spec builder.
BreakingFieldSameDefaultRuleSpecBuilder = &bufcheckserverutil.RuleSpecBuilder{
ID: "FIELD_SAME_DEFAULT",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,45 @@ func handleBreakingFieldSameJavaUTF8Validation(
return nil
}

// HandleBreakingFieldSameGoStripEnumPrefix is a check function.
var HandleBreakingFieldSameGoStripEnumPrefix = bufcheckserverutil.NewBreakingEnumPairRuleHandler(handleBreakingFieldSameGoStripEnumPrefix)

func handleBreakingFieldSameGoStripEnumPrefix(
responseWriter bufcheckserverutil.ResponseWriter,
request bufcheckserverutil.Request,
enum bufprotosource.Enum,
previousEnum bufprotosource.Enum,
) error {
previousDescriptor, err := previousEnum.AsDescriptor()
if err != nil {
return err
}
descriptor, err := enum.AsDescriptor()
if err != nil {
return err
}
previousPrefix, err := enumGoStripEnumPrefix(previousDescriptor)
if err != nil {
return err
}
prefix, err := enumGoStripEnumPrefix(descriptor)
if err != nil {
return err
}
if previousPrefix != prefix {
responseWriter.AddProtosourceAnnotationf(
enumGoStripEnumPrefixLocation(enum),
enumGoStripEnumPrefixLocation(previousEnum),
enum.File().Path(),
`%s changed Go strip enum prefix from %q to %q.`,
enum.Name(),
previousPrefix,
prefix,
)
}
return nil
}

// HandleBreakingFieldSameJSType is a check function.
var HandleBreakingFieldSameJSType = bufcheckserverutil.NewBreakingFieldPairRuleHandler(handleBreakingFieldSameJSType)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ import (
"github.com/bufbuild/protocompile/protoutil"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/descriptorpb"
gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
)

const (
featuresFieldName = "features"
featureNameUTF8Validation = "utf8_validation"
featureNameJSONFormat = "json_format"
cppFeatureNameStringType = "string_type"
javaFeatureNameUTF8Validation = "utf8_validation"
featuresFieldName = "features"
featureNameUTF8Validation = "utf8_validation"
featureNameJSONFormat = "json_format"
cppFeatureNameStringType = "string_type"
javaFeatureNameUTF8Validation = "utf8_validation"
goFeatureNameStripEnumPrefix = "strip_enum_prefix"
goFeatureNameAPILevel = "api_level"
)

var (
Expand Down Expand Up @@ -257,6 +260,20 @@ func fieldJavaUTF8ValidationLocation(field bufprotosource.Field) bufprotosource.
return getCustomFeatureLocation(field, ext, javaFeatureNameUTF8Validation)
}

func enumGoStripEnumPrefix(enum protoreflect.EnumDescriptor) (gofeaturespb.GoFeatures_StripEnumPrefix, error) {
val, err := customfeatures.ResolveGoFeatureForEnum(enum, goFeatureNameStripEnumPrefix, protoreflect.EnumKind)
if err != nil {
return 0, err
}
return gofeaturespb.GoFeatures_StripEnumPrefix(val.Enum()), nil
}

func enumGoStripEnumPrefixLocation(enum bufprotosource.Enum) bufprotosource.Location {
// For enums, we use the enum's features location
// This is similar to how other enum features are handled
return enum.Features().EnumTypeLocation()
}

func getCustomFeatureLocation(field bufprotosource.Field, extension protoreflect.ExtensionTypeDescriptor, fieldName protoreflect.Name) bufprotosource.Location {
if extension.Message() == nil {
return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"fmt"

"github.com/bufbuild/buf/private/gen/proto/go/google/protobuf"
gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"

"github.com/bufbuild/protocompile/protoutil"
"google.golang.org/protobuf/reflect/protoreflect"
)
Expand All @@ -34,6 +36,18 @@ func ResolveJavaFeature(field protoreflect.FieldDescriptor, fieldName protorefle
return resolveFeature(field, protobuf.E_Java.TypeDescriptor(), fieldName, expectedKind)
}

// ResolveGoFeature returns a value for the given field name of the (pb.go) custom feature
// for the given field.
func ResolveGoFeature(field protoreflect.FieldDescriptor, fieldName protoreflect.Name, expectedKind protoreflect.Kind) (protoreflect.Value, error) {
return resolveFeature(field, gofeaturespb.E_Go.TypeDescriptor(), fieldName, expectedKind)
}

// ResolveGoFeatureForEnum returns a value for the given field name of the (pb.go) custom feature
// for the given enum.
func ResolveGoFeatureForEnum(enum protoreflect.EnumDescriptor, fieldName protoreflect.Name, expectedKind protoreflect.Kind) (protoreflect.Value, error) {
return resolveFeatureForEnum(enum, gofeaturespb.E_Go.TypeDescriptor(), fieldName, expectedKind)
}

func resolveFeature(
field protoreflect.FieldDescriptor,
extension protoreflect.ExtensionTypeDescriptor,
Expand All @@ -54,3 +68,24 @@ func resolveFeature(
featureField,
)
}

func resolveFeatureForEnum(
enum protoreflect.EnumDescriptor,
extension protoreflect.ExtensionTypeDescriptor,
fieldName protoreflect.Name,
expectedKind protoreflect.Kind,
) (protoreflect.Value, error) {
featureField := extension.Message().Fields().ByName(fieldName)
if featureField == nil {
return protoreflect.Value{}, fmt.Errorf("unable to resolve field descriptor for %s.%s", extension.Message().FullName(), fieldName)
}
if featureField.Kind() != expectedKind || featureField.IsList() {
return protoreflect.Value{}, fmt.Errorf("resolved field descriptor for %s.%s has unexpected type: expected optional %s, got %s %s",
extension.Message().FullName(), fieldName, expectedKind, featureField.Cardinality(), featureField.Kind())
}
return protoutil.ResolveCustomFeature(
enum,
extension.Type(),
featureField,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"testing"

"github.com/bufbuild/buf/private/gen/proto/go/google/protobuf"
gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/descriptorpb"
Expand All @@ -40,3 +41,12 @@ func TestResolveJavaFeatures(t *testing.T) {
// This will use the default value for proto2
require.Equal(t, protobuf.JavaFeatures_DEFAULT.Number(), val.Enum())
}

func TestResolveGoFeatures(t *testing.T) {
t.Parallel()
field := (*descriptorpb.FileDescriptorProto)(nil).ProtoReflect().Descriptor().Fields().ByName("package")
val, err := ResolveGoFeature(field, "api_level", protoreflect.EnumKind)
require.NoError(t, err)
// This will use the default value for proto2
require.Equal(t, gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED.Number(), val.Enum())
}
22 changes: 22 additions & 0 deletions private/bufpkg/bufconfig/generate_managed_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"strings"

"google.golang.org/protobuf/types/descriptorpb"
gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
)

// FileOption is a file option.
Expand Down Expand Up @@ -67,6 +68,8 @@ const (
FileOptionRubyPackageSuffix
// FileOptionSwiftPrefix is the file option swift_prefix.
FileOptionSwiftPrefix
// FileOptionGoApiLevel is the Go API level feature (pb.go).api_level.
FileOptionGoApiLevel
)

// String implements fmt.Stringer.
Expand Down Expand Up @@ -120,6 +123,7 @@ var (
FileOptionRubyPackage: "ruby_package",
FileOptionRubyPackageSuffix: "ruby_package_suffix",
FileOptionSwiftPrefix: "swift_prefix",
FileOptionGoApiLevel: "features.(pb.go).api_level",
}
stringToFileOption = map[string]FileOption{
"java_package": FileOptionJavaPackage,
Expand All @@ -141,6 +145,7 @@ var (
"ruby_package": FileOptionRubyPackage,
"ruby_package_suffix": FileOptionRubyPackageSuffix,
"swift_prefix": FileOptionSwiftPrefix,
"features.(pb.go).api_level": FileOptionGoApiLevel,
}
fileOptionToParseOverrideValueFunc = map[FileOption]func(any) (any, error){
FileOptionJavaPackage: parseOverrideValue[string],
Expand All @@ -162,6 +167,7 @@ var (
FileOptionRubyPackage: parseOverrideValue[string],
FileOptionRubyPackageSuffix: parseOverrideValue[string],
FileOptionSwiftPrefix: parseOverrideValue[string],
FileOptionGoApiLevel: parseOverrideValueGoApiLevel,
}
fieldOptionToString = map[FieldOption]string{
FieldOptionJSType: "jstype",
Expand Down Expand Up @@ -230,6 +236,18 @@ func parseOverrideValueJSType(override any) (any, error) {
return descriptorpb.FieldOptions_JSType(jsTypeEnum), nil
}

func parseOverrideValueGoApiLevel(override any) (any, error) {
apiLevelName, ok := override.(string)
if !ok {
return nil, errors.New("must be one of API_LEVEL_UNSPECIFIED, API_OPEN, API_HYBRID, or API_OPAQUE")
}
apiLevelEnum, ok := gofeaturespb.GoFeatures_APILevel_value[apiLevelName]
if !ok {
return nil, errors.New("must be one of API_LEVEL_UNSPECIFIED, API_OPEN, API_HYBRID, or API_OPAQUE")
}
return gofeaturespb.GoFeatures_APILevel(apiLevelEnum), nil
}

// If the file or field option override value is one of the supported enum types,
// then we want to write out the string representation of the enum value, not
// the corresponding int32.
Expand Down Expand Up @@ -271,6 +289,10 @@ func getOverrideValue(fileOptionName string, fieldOptionName string, value any)
if optimizeModeValue, ok := value.(descriptorpb.FileOptions_OptimizeMode); ok {
return optimizeModeValue.String(), nil
}
case FileOptionGoApiLevel:
if apiLevelValue, ok := value.(gofeaturespb.GoFeatures_APILevel); ok {
return apiLevelValue.String(), nil
}
}
}
if fieldOptionName != "" {
Expand Down