Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand buf lint PROTOVALIDATE rule to lint example and predefined rules #3317

Merged
merged 39 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d56a84b
validate.Constraint -> shared.Constraint
oliversun9 Sep 5, 2024
5779a5e
add comment todos
oliversun9 Sep 5, 2024
c3d13e5
wip
oliversun9 Sep 10, 2024
ce41891
add comments and prints (to be removed); still need to get map to work
oliversun9 Sep 10, 2024
89e7440
checkpoint; works
oliversun9 Sep 11, 2024
cf88e02
checkpoint; seems to work
oliversun9 Sep 12, 2024
1ca775b
clean up shared rules
oliversun9 Sep 12, 2024
c664274
move
oliversun9 Sep 12, 2024
49452e0
small cleanup
oliversun9 Sep 12, 2024
e7f0f34
rearrange for less diff
oliversun9 Sep 13, 2024
0bfd6ff
rename
oliversun9 Sep 13, 2024
452d1af
add back reparsing, which was dropped by mistake; cleanup
oliversun9 Sep 13, 2024
b726162
add commit info
oliversun9 Sep 13, 2024
74c86d2
comment
oliversun9 Sep 13, 2024
c16b0bd
update protovalidate files in test
oliversun9 Sep 13, 2024
6d7f0a5
Merge branch 'main' into osun/lint-examples
oliversun9 Sep 13, 2024
558bbf9
Merge remote-tracking branch 'origin/main' into osun/lint-examples
doriable Sep 25, 2024
f50049d
Update shared rules -> predefined rules.
doriable Sep 25, 2024
282e462
Fix CI
doriable Sep 25, 2024
c6486f4
Fix CI for real
doriable Sep 25, 2024
bae26dd
Small refactors and address in-line TODOs
doriable Sep 25, 2024
46f4d81
Merge remote-tracking branch 'origin/main' into osun/lint-examples
doriable Sep 26, 2024
3ddf6c2
Remove the use of proto.{Marshal, Unmarshal}
doriable Sep 26, 2024
c36ef8f
Merge remote-tracking branch 'origin/main' into osun/lint-examples
doriable Sep 26, 2024
75898ac
Refactor again
doriable Sep 26, 2024
eae0426
Add test cases for examples and fix bugs found
doriable Sep 27, 2024
e1bb066
Handle type casting
doriable Sep 27, 2024
2bdb9ff
Address last todo
doriable Sep 27, 2024
e3c766a
Add predefined rules tests (WIP)
doriable Sep 30, 2024
b4c08ef
Merge remote-tracking branch 'origin/main' into osun/lint-examples
doriable Sep 30, 2024
48c393a
Merge remote-tracking branch 'origin/main' into osun/lint-examples
doriable Sep 30, 2024
97f260f
Upgrade fix from protovalidate-go and update some tests
doriable Oct 1, 2024
d0ec45a
Merge remote-tracking branch 'origin/main' into osun/lint-examples
doriable Oct 1, 2024
6019ed1
Fix CI
doriable Oct 1, 2024
49da005
Address comments
doriable Oct 1, 2024
4f73316
Update comment
doriable Oct 2, 2024
0571da6
Merge branch 'main' into osun/lint-examples
bufdev Oct 3, 2024
02d8762
Update CHANGELOG.md
bufdev Oct 3, 2024
a053f42
Merge branch 'osun/lint-examples' of https://github.com/bufbuild/buf …
bufdev Oct 3, 2024
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
6 changes: 6 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,9 @@ issues:
- forbidigo
path: private/pkg/protoencoding
text: "prototext.Unmarshal"
- linters:
- gosec
# This checks the cel constraints for predefined rules from an Image, and converts loop indices to int32
# to set the source path for the location, this operation should be safe.
path: private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go
text: "G115:"
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
connectrpc.com/otelconnect v0.7.1
github.com/bufbuild/protocompile v0.14.1
github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a
github.com/bufbuild/protovalidate-go v0.7.0
github.com/bufbuild/protovalidate-go v0.7.1
github.com/docker/docker v27.3.1+incompatible
github.com/go-chi/chi/v5 v5.1.0
github.com/gofrs/flock v0.12.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a h1:l3RhVoG0RtC61h6TVWnkniGj4TgBebuyPQRdleFAmTg=
github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ=
github.com/bufbuild/protovalidate-go v0.7.0 h1:MYU9GSZM7TSsWNywvyXoEc8y3kc1MNqD3k5mddIBEL4=
github.com/bufbuild/protovalidate-go v0.7.0/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4=
github.com/bufbuild/protovalidate-go v0.7.1 h1:ac50NTO6+1+mKg5sP/GBPLlMkQFeI+OeaYGFdS1vu98=
github.com/bufbuild/protovalidate-go v0.7.1/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand Down
5 changes: 4 additions & 1 deletion make/buf/all.mk
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ LICENSE_HEADER_LICENSE_TYPE := apache
LICENSE_HEADER_COPYRIGHT_HOLDER := Buf Technologies, Inc.
LICENSE_HEADER_YEAR_RANGE := 2020-2024
LICENSE_HEADER_IGNORES := \/testdata enterprise
PROTOVALIDATE_VERSION := v0.7.1
PROTOVALIDATE_VERSION := v0.8.1
# Comment out to use released buf
BUF_GO_INSTALL_PATH := ./cmd/buf

Expand Down Expand Up @@ -144,6 +144,9 @@ bufgeneratebuflinttestdata:
$(BUF_BIN) export \
buf.build/bufbuild/protovalidate:$(PROTOVALIDATE_VERSION) \
--output private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate
$(BUF_BIN) export \
bufdev marked this conversation as resolved.
Show resolved Hide resolved
buf.build/bufbuild/protovalidate:$(PROTOVALIDATE_VERSION) \
--output private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/vendor/protovalidate

bufgeneratesteps:: \
bufgeneratego \
Expand Down
9 changes: 3 additions & 6 deletions private/buf/cmd/buf/buf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2907,8 +2907,7 @@ testdata/check_plugins/current/proto/api/v1/service.proto:17:14:RPC request type
testdata/check_plugins/current/proto/api/v1/service.proto:17:42:RPC response type "GetFooTestResponse" should be named "GetFooResponse" or "FooServiceTestGetFooResponse".
testdata/check_plugins/current/proto/api/v1/service.proto:26:1:"ListFooResponse" is a pagination response without a page token field named "page_token" (buf-plugin-rpc-ext)
testdata/check_plugins/current/proto/common/v1alpha1/messages.proto:16:5:field "common.v1alpha1.Four.FourTwo.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
testdata/check_plugins/current/vendor/protovalidate/buf/validate/expression.proto:42:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
testdata/check_plugins/current/vendor/protovalidate/buf/validate/priv/private.proto:38:3:field "buf.validate.priv.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
testdata/check_plugins/current/vendor/protovalidate/buf/validate/validate.proto:94:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
`),
"lint",
filepath.Join("testdata", "check_plugins", "current"),
Expand All @@ -2934,8 +2933,7 @@ testdata/check_plugins/current/proto/api/v1/service.proto:17:14:RPC request type
testdata/check_plugins/current/proto/api/v1/service.proto:17:42:RPC response type "GetFooTestResponse" should be named "GetFooResponse" or "FooServiceTestGetFooResponse".
testdata/check_plugins/current/proto/api/v1/service.proto:26:1:"ListFooResponse" is a pagination response without a page token field named "page_token" (buf-plugin-rpc-ext)
testdata/check_plugins/current/proto/common/v1alpha1/messages.proto:16:5:field "common.v1alpha1.Four.FourTwo.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
testdata/check_plugins/current/vendor/protovalidate/buf/validate/expression.proto:42:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
testdata/check_plugins/current/vendor/protovalidate/buf/validate/priv/private.proto:38:3:field "buf.validate.priv.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
testdata/check_plugins/current/vendor/protovalidate/buf/validate/validate.proto:94:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
`),
"lint",
filepath.Join("testdata", "check_plugins", "current"),
Expand Down Expand Up @@ -2985,8 +2983,7 @@ testdata/check_plugins/current/vendor/protovalidate/buf/validate/priv/private.pr
filepath.FromSlash(`
testdata/check_plugins/current/proto/api/v1/service.proto:11:1:Service name "api.v1.FooServiceMock" has banned suffix "Mock". (buf-plugin-suffix)
testdata/check_plugins/current/proto/common/v1alpha1/messages.proto:16:5:field "common.v1alpha1.Four.FourTwo.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
testdata/check_plugins/current/vendor/protovalidate/buf/validate/expression.proto:42:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
testdata/check_plugins/current/vendor/protovalidate/buf/validate/priv/private.proto:38:3:field "buf.validate.priv.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
testdata/check_plugins/current/vendor/protovalidate/buf/validate/validate.proto:94:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext)
`),
"lint",
filepath.Join("testdata", "check_plugins", "current"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"github.com/bufbuild/buf/private/bufpkg/bufcheck/internal/bufcheckopt"
"github.com/bufbuild/buf/private/bufpkg/bufprotosource"
"github.com/bufbuild/buf/private/pkg/normalpath"
"github.com/bufbuild/buf/private/pkg/protodescriptor"
"github.com/bufbuild/buf/private/pkg/protoencoding"
"github.com/bufbuild/buf/private/pkg/protoversion"
"github.com/bufbuild/buf/private/pkg/slicesext"
"github.com/bufbuild/buf/private/pkg/stringutil"
Expand Down Expand Up @@ -935,17 +937,18 @@ func handleLintPackageVersionSuffix(
}

// HandleLintProtovalidate is a handle function.
var HandleLintProtovalidate = bufcheckserverutil.NewMultiHandler(
// NOTE: Oneofs also have protovalidate support, but they only have a "required" field, so nothing to lint.
bufcheckserverutil.NewLintMessageRuleHandler(handleLintMessageProtovalidate),
bufcheckserverutil.NewLintFieldRuleHandler(handleLintFieldProtovalidate),
)
var HandleLintProtovalidate = bufcheckserverutil.NewRuleHandler(handleLintProtovalidate)

func handleLintMessageProtovalidate(
// handleLintProtovalidate runs checks all predefined rules, message rules, and field rules.
//
// NOTE: Oneofs also have protovalidate support, but they only have a "required" field, so nothing to lint.
func handleLintProtovalidate(
ctx context.Context,
responseWriter bufcheckserverutil.ResponseWriter,
_ bufcheckserverutil.Request,
message bufprotosource.Message,
request bufcheckserverutil.Request,
) error {
// TODO: addAnnotationFunc is used to set add annotations to responseWriter. A follow-up
// will be made to refactor the code so we no longer need this.
addAnnotationFunc := func(
_ bufprotosource.Descriptor,
location bufprotosource.Location,
Expand All @@ -960,29 +963,49 @@ func handleLintMessageProtovalidate(
args...,
)
}
return buflintvalidate.CheckMessage(addAnnotationFunc, message)
}

func handleLintFieldProtovalidate(
responseWriter bufcheckserverutil.ResponseWriter,
_ bufcheckserverutil.Request,
field bufprotosource.Field,
) error {
addAnnotationFunc := func(
_ bufprotosource.Descriptor,
location bufprotosource.Location,
_ []bufprotosource.Location,
format string,
args ...interface{},
) {
responseWriter.AddProtosourceAnnotation(
location,
nil,
format,
args...,
)
// We create a new extension resolver using all of the files from the request, including
// import files. This is because there can be a case where a non-import file uses a predefined
// rule from an imported file.
extensionResolver, err := protoencoding.NewResolver(
slicesext.Map(
request.ProtosourceFiles(),
func(protosourceFile bufprotosource.File) protodescriptor.FileDescriptor {
return protosourceFile.FileDescriptor()
},
)...,
)
if err != nil {
return err
}
// However, we only want to check non-import files, so we can use NewLintMessageRuleHandler
// and NewLintFieldRuleHandler utils to check messages and fields respectively.
if err := bufcheckserverutil.NewLintMessageRuleHandler(
func(
_ bufcheckserverutil.ResponseWriter,
_ bufcheckserverutil.Request,
message bufprotosource.Message,
) error {
return buflintvalidate.CheckMessage(addAnnotationFunc, message)
},
// The responseWriter is being passed in through the shared addAnnotationFunc, so we
// do not pass in responseWriter again. This should be addressed in a refactor.
).Handle(ctx, nil, request); err != nil {
return err
}
return buflintvalidate.CheckField(addAnnotationFunc, field)
return bufcheckserverutil.NewLintFieldRuleHandler(
func(
_ bufcheckserverutil.ResponseWriter,
_ bufcheckserverutil.Request,
field bufprotosource.Field,
) error {
if err := buflintvalidate.CheckPredefinedRuleExtension(addAnnotationFunc, field, extensionResolver); err != nil {
return err
}
return buflintvalidate.CheckField(addAnnotationFunc, field, extensionResolver)
},
// The responseWriter is being passed in through the shared addAnnotationFunc, so we
// do not pass in responseWriter again. This should be addressed in a refactor.
).Handle(ctx, nil, request)
}

// HandleLintRPCNoClientStreaming is a handle function.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ package buflintvalidate
import (
"buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
"github.com/bufbuild/buf/private/bufpkg/bufprotosource"
"github.com/bufbuild/buf/private/pkg/protoencoding"
"github.com/bufbuild/protovalidate-go/resolver"
)

// https://buf.build/bufbuild/protovalidate/docs/v0.5.1:buf.validate#buf.validate.MessageConstraints
const disabledFieldNumberInMesageConstraints = 1

// CheckMessage validates that all rules on the message are valid, and any CEL expressions compile.
//
// addAnnotationFunc adds an annotation with the descriptor and location for check results.
// It also checks all predefined rule extensions on the messages.
func CheckMessage(
// addAnnotationFunc adds an annotation with the descriptor and location for check results.
addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}),
message bufprotosource.Message,
) error {
Expand Down Expand Up @@ -55,13 +56,23 @@ func CheckMessage(
// CheckField validates that all rules on the field are valid, and any CEL expressions compile.
//
// For a set of rules to be valid, it must
// 1. permit _some_ value
// 1. permit _some_ value and all example values, if any
// 2. have a type compatible with the field it validates.
//
// addAnnotationFunc adds an annotation with the descriptor and location for check results.
func CheckField(
// addAnnotationFunc adds an annotation with the descriptor and location for check results.
addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}),
field bufprotosource.Field,
extensionTypeResolver protoencoding.Resolver,
) error {
return checkField(addAnnotationFunc, field, extensionTypeResolver)
}

// CheckPredefinedRuleExtension checks that a predefined extension is valid, and any CEL expressions compile.
func CheckPredefinedRuleExtension(
// addAnnotationFunc adds an annotation with the descriptor and location for check results.
addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}),
field bufprotosource.Field,
extensionResolver protoencoding.Resolver,
) error {
return checkField(addAnnotationFunc, field)
return checkPredefinedRuleExtension(addAnnotationFunc, field, extensionResolver)
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,16 @@ func checkCELForField(
return nil
}

// Returns true only if all cel expressions compile
func checkCEL(
celEnv *cel.Env,
celConstraints []*validate.Constraint,
parentName string,
parentNameCapitalized string,
celName string,
add func(int, string, ...interface{}),
) {
) bool {
allCelExpressionsCompile := true
idToConstraintIndices := make(map[string][]int, len(celConstraints))
for i, celConstraint := range celConstraints {
if celID := celConstraint.GetId(); celID != "" {
Expand All @@ -142,7 +144,7 @@ func checkCEL(
} else {
add(i, "%s has an empty %s.id. IDs should always be specified.", parentNameCapitalized, celName)
}
if celConstraint.GetExpression() == "" {
if len(strings.TrimSpace(celConstraint.GetExpression())) == 0 {
add(i, "%s has an empty %s.expression. Expressions should always be specified.", parentNameCapitalized, celName)
continue
}
Expand Down Expand Up @@ -179,6 +181,7 @@ func checkCEL(
)
}
if compileIssues.Err() != nil {
allCelExpressionsCompile = false
for _, parsedIssue := range parseCelIssuesText(compileIssues.Err().Error()) {
add(
i,
Expand All @@ -204,6 +207,7 @@ func checkCEL(
)
}
}
return allCelExpressionsCompile
}

// this depends on the undocumented behavior of cel-go's error message
Expand Down
Loading