From 6ea2d7ee3fb31b81696d0fbf4d69d95ac4bb9a6f Mon Sep 17 00:00:00 2001 From: "Philip K. Warren" Date: Wed, 15 Apr 2026 12:05:25 -0500 Subject: [PATCH 1/2] Add an option to override the protovalidate validator Validator instances are not cheap, so by default use the global validator instead of allocating a new one in the server handlers. Add options to allow users to override the validator if needed. The other changes are code hygiene - updating to support the last two versions of Go and bumping golangci-lint to the latest v2. --- .github/workflows/ci.yaml | 4 +- .golangci.yml | 179 ++++++++++-------- Makefile | 14 +- check/category.go | 2 +- check/check_service_handler.go | 15 +- check/check_service_handler_test.go | 9 +- check/checktest/checktest.go | 2 +- check/client_test.go | 9 +- .../buf-plugin-field-lower-snake-case/main.go | 2 +- check/request.go | 7 +- check/rule.go | 6 +- check/server.go | 21 +- go.mod | 33 ++-- go.sum | 76 ++++---- info/plugin_info_service_handler.go | 28 ++- info/plugin_info_service_handler_test.go | 3 +- internal/pkg/cache/singleton_test.go | 2 +- internal/pkg/thread/thread.go | 6 +- internal/pkg/thread/thread_test.go | 4 +- option/errors.go | 2 +- 20 files changed, 230 insertions(+), 194 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9f5946e..ec3bc36 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: [1.23.x, 1.24.x, 1.25.x] + go-version: [1.25.x, 1.26.x] steps: - name: Checkout Code uses: actions/checkout@v6 @@ -32,5 +32,5 @@ jobs: # conflicting guidance, run only on the most recent supported version. # For the same reason, only check generated code on the most recent # supported version. - if: matrix.go-version == '1.25.x' + if: matrix.go-version == '1.26.x' run: make checkgenerate && make lint diff --git a/.golangci.yml b/.golangci.yml index c458d62..d20ed67 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,25 +1,6 @@ -linters-settings: - errcheck: - check-type-assertions: true - forbidigo: - forbid: - - '^fmt\.Print' - - '^log\.' - - '^print$' - - '^println$' - - '^panic$' - godox: - # TODO, OPT, etc. comments are fine to commit. Use FIXME comments for - # temporary hacks, and use godox to prevent committing them. - keywords: [FIXME] - varnamelen: - ignore-decls: - - T any - - i int - - wg sync.WaitGroup - - id string +version: "2" linters: - enable-all: true + default: all disable: - cyclop # covered by gocyclo - depguard # unnecessary for small libraries @@ -28,9 +9,6 @@ linters: - funlen # rely on code review to limit function length - gochecknoglobals # many exceptions - gocognit # dubious "cognitive overhead" quantification - - gofumpt # prefer standard gofmt - - goimports # rely on gci instead - - gomnd # some unnamed constants are okay - inamedparam # not standard style - interfacebloat # many exceptions - ireturn # "accept interfaces, return structs" isn't ironclad @@ -38,68 +16,101 @@ linters: - maintidx # covered by gocyclo - nilnil # allow this - nlreturn # generous whitespace violates house style + - noinlineerr # excess scope violates house style - testifylint # does not want us to use assert - testpackage # internal tests are fine - thelper # we want to print out the whole stack - wrapcheck # don't _always_ need to wrap errors - wsl # generous whitespace violates house style -issues: - exclude-dirs-use-default: false - exclude-rules: - - linters: - - revive - path: check/client.go - test: "CheckCallOption" - - linters: - - revive - path: check/check_service_handler.go - test: "CheckServiceHandlerOption" - - linters: - - exhaustive - path: option/options.go - text: "reflect.Pointer|reflect.Ptr" - - linters: - - gocritic - path: check/file.go - text: "commentFormatting" - - linters: - - gocritic - path: check/location.go - text: "commentFormatting" - - linters: - - unparam - path: check/category_spec.go - - linters: - - unparam - path: check/annotation.go - - linters: - - unparam - path: check/response.go - - linters: - - unparam - path: info/plugin_info.go - - linters: - - varnamelen - path: check/internal/example - - linters: - - dupl - path: check/checkutil/breaking.go - - linters: - - varnamelen - path: check/checkutil/breaking.go - - linters: - - varnamelen - path: check/checkutil/lint.go - - linters: - - varnamelen - path: check/checkutil/util.go - - linters: - - varnamelen - path: internal/pkg/xslices/xslices.go - - linters: - - revive - path: internal/pkg/compare/compare.go - - linters: - - gosec - path: check/checktest/checktest.go - text: "G115:" + - wsl_v5 # generous whitespace violates house style + settings: + errcheck: + check-type-assertions: true + forbidigo: + forbid: + - pattern: ^fmt\.Print + - pattern: ^log\. + - pattern: ^print$ + - pattern: ^println$ + - pattern: ^panic$ + godox: + # TODO, OPT, etc. comments are fine to commit. Use FIXME comments for + # temporary hacks, and use godox to prevent committing them. + keywords: + - FIXME + varnamelen: + ignore-decls: + - T any + - i int + - wg sync.WaitGroup + - id string + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - revive + path: check/client.go + text: CheckCallOption + - linters: + - revive + path: check/check_service_handler.go + text: stutter + - linters: + - exhaustive + path: option/options.go + text: reflect.Pointer|reflect.Ptr + - linters: + - gocritic + path: check/file.go + text: commentFormatting + - linters: + - gocritic + path: check/location.go + text: commentFormatting + - linters: + - unparam + path: check/category_spec.go + - linters: + - unparam + path: check/annotation.go + - linters: + - unparam + path: check/response.go + - linters: + - unparam + path: info/plugin_info.go + - linters: + - varnamelen + path: check/internal/example + - linters: + - dupl + path: check/checkutil/breaking.go + - linters: + - varnamelen + path: check/checkutil/breaking.go + - linters: + - varnamelen + path: check/checkutil/lint.go + - linters: + - varnamelen + path: check/checkutil/util.go + - linters: + - varnamelen + path: internal/pkg/xslices/xslices.go + - linters: + - revive + path: internal/pkg/compare/compare.go + - linters: + - gosec + path: check/checktest/checktest.go + text: 'G115:' +formatters: + enable: + - gci + exclusions: + generated: lax diff --git a/Makefile b/Makefile index cef2e6d..9e07e48 100644 --- a/Makefile +++ b/Makefile @@ -12,11 +12,8 @@ export GOBIN := $(abspath $(BIN)) COPYRIGHT_YEARS := 2024-2025 LICENSE_IGNORE := --ignore testdata/ -BUF_VERSION := v1.50.0 -GO_MOD_GOTOOLCHAIN := go1.23.5 -GOLANGCI_LINT_VERSION := v1.63.4 -# https://github.com/golangci/golangci-lint/issues/4837 -GOLANGCI_LINT_GOTOOLCHAIN := $(GO_MOD_GOTOOLCHAIN) +BUF_VERSION := v1.67.0 +GOLANGCI_LINT_VERSION := v2.11.4 #GO_GET_PKGS := .PHONY: help @@ -48,11 +45,11 @@ install: ## Install all binaries .PHONY: lint lint: $(BIN)/golangci-lint ## Lint go vet ./... - GOTOOLCHAIN=$(GOLANGCI_LINT_GOTOOLCHAIN) golangci-lint run --modules-download-mode=readonly --timeout=3m0s + golangci-lint run --modules-download-mode=readonly --timeout=3m0s .PHONY: lintfix lintfix: $(BIN)/golangci-lint ## Automatically fix some lint errors - GOTOOLCHAIN=$(GOLANGCI_LINT_GOTOOLCHAIN) golangci-lint run --fix --modules-download-mode=readonly --timeout=3m0s + golangci-lint run --fix --modules-download-mode=readonly --timeout=3m0s .PHONY: generate generate: $(BIN)/buf $(BIN)/protoc-gen-pluginrpc-go $(BIN)/license-header ## Regenerate code and licenses @@ -65,7 +62,6 @@ generate: $(BIN)/buf $(BIN)/protoc-gen-pluginrpc-go $(BIN)/license-header ## Reg .PHONY: upgrade upgrade: ## Upgrade dependencies - go mod edit -toolchain=$(GO_MOD_GOTOOLCHAIN) go get -u -t ./... $(GO_GET_PKGS) go mod tidy -v @@ -84,7 +80,7 @@ $(BIN)/license-header: Makefile $(BIN)/golangci-lint: Makefile @mkdir -p $(@D) - GOTOOLCHAIN=$(GOLANGCI_LINT_GOTOOLCHAIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) .PHONY: $(BIN)/protoc-gen-pluginrpc-go $(BIN)/protoc-gen-pluginrpc-go: diff --git a/check/category.go b/check/category.go index 8f5300d..63bc11d 100644 --- a/check/category.go +++ b/check/category.go @@ -35,7 +35,7 @@ type Category interface { // // This uniquely identifies the Category. ID() string - // A user-displayable purpose of the category. + // Purpose is a user-displayable purpose of the category. // // Always present. Purpose() string diff --git a/check/check_service_handler.go b/check/check_service_handler.go index 04f8fb7..ad78e43 100644 --- a/check/check_service_handler.go +++ b/check/check_service_handler.go @@ -55,6 +55,14 @@ func CheckServiceHandlerWithParallelism(parallelism int) CheckServiceHandlerOpti } } +// CheckServiceHandlerWithValidator allows overriding the default validator used by the handler. +// By default, [protovalidate.GlobalValidator] is used. +func CheckServiceHandlerWithValidator(validator protovalidate.Validator) CheckServiceHandlerOption { + return func(checkServiceHandlerOptions *checkServiceHandlerOptions) { + checkServiceHandlerOptions.validator = validator + } +} + // *** PRIVATE *** type checkServiceHandler struct { @@ -118,9 +126,9 @@ func newCheckServiceHandler(spec *Spec, options ...CheckServiceHandlerOption) (* ruleIDToRule[id] = rule ruleIDToIndex[id] = i } - validator, err := protovalidate.New() - if err != nil { - return nil, err + validator := checkServiceHandlerOptions.validator + if validator == nil { + validator = protovalidate.GlobalValidator } return &checkServiceHandler{ spec: spec, @@ -300,6 +308,7 @@ func (c *checkServiceHandler) getCategoriesAndNextPageToken(pageSize int, pageTo type checkServiceHandlerOptions struct { parallelism int + validator protovalidate.Validator } func newCheckServiceHandlerOptions() *checkServiceHandlerOptions { diff --git a/check/check_service_handler_test.go b/check/check_service_handler_test.go index 4aeae60..67fc7b4 100644 --- a/check/check_service_handler_test.go +++ b/check/check_service_handler_test.go @@ -15,7 +15,6 @@ package check import ( - "context" "testing" checkv1 "buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go/buf/plugin/check/v1" @@ -39,7 +38,7 @@ func TestCheckServiceHandlerUniqueFiles(t *testing.T) { require.NoError(t, err) _, err = checkServiceHandler.Check( - context.Background(), + t.Context(), &checkv1.CheckRequest{ FileDescriptors: []*descriptorv1.FileDescriptor{ { @@ -62,7 +61,7 @@ func TestCheckServiceHandlerUniqueFiles(t *testing.T) { require.NoError(t, err) _, err = checkServiceHandler.Check( - context.Background(), + t.Context(), &checkv1.CheckRequest{ FileDescriptors: []*descriptorv1.FileDescriptor{ { @@ -85,7 +84,7 @@ func TestCheckServiceHandlerUniqueFiles(t *testing.T) { require.Equal(t, pluginrpc.CodeInvalidArgument, pluginrpcError.Code()) _, err = checkServiceHandler.Check( - context.Background(), + t.Context(), &checkv1.CheckRequest{ FileDescriptors: []*descriptorv1.FileDescriptor{ { @@ -129,7 +128,7 @@ func TestCheckServiceHandlerNoSourceCodeInfo(t *testing.T) { require.NoError(t, err) _, err = checkServiceHandler.Check( - context.Background(), + t.Context(), &checkv1.CheckRequest{ FileDescriptors: []*descriptorv1.FileDescriptor{ { diff --git a/check/checktest/checktest.go b/check/checktest/checktest.go index 0338c12..66ef3d2 100644 --- a/check/checktest/checktest.go +++ b/check/checktest/checktest.go @@ -76,7 +76,7 @@ type CheckTest struct { // - Call Check on the Client. // - Compare the resulting Annotations with the ExpectedAnnotations, failing if there is a mismatch. func (c CheckTest) Run(t *testing.T) { - ctx := context.Background() + ctx := t.Context() require.NotNil(t, c.Request) require.NotNil(t, c.Spec) diff --git a/check/client_test.go b/check/client_test.go index 54139f3..a3b4a01 100644 --- a/check/client_test.go +++ b/check/client_test.go @@ -15,7 +15,6 @@ package check import ( - "context" "fmt" "slices" "testing" @@ -34,7 +33,7 @@ func TestClientListRulesCategoriesSimple(t *testing.T) { } func testClientListRulesCategoriesSimple(t *testing.T, options ...ClientForSpecOption) { - ctx := context.Background() + ctx := t.Context() client, err := NewClientForSpec( &Spec{ Rules: []*RuleSpec{ @@ -147,7 +146,7 @@ func testClientListRulesCount(t *testing.T, count int) { slices.Reverse(ruleSpecsOutOfOrder) client, err := NewClientForSpec(&Spec{Rules: ruleSpecsOutOfOrder}) require.NoError(t, err) - rules, err := client.ListRules(context.Background()) + rules, err := client.ListRules(t.Context()) require.NoError(t, err) require.Equal(t, count, len(rules)) for i := range count { @@ -175,7 +174,7 @@ func TestPluginInfo(t *testing.T) { }, ) require.NoError(t, err) - pluginInfo, err := client.GetPluginInfo(context.Background()) + pluginInfo, err := client.GetPluginInfo(t.Context()) require.NoError(t, err) license := pluginInfo.License() require.NotNil(t, license) @@ -201,7 +200,7 @@ func TestPluginInfoUnimplemented(t *testing.T) { }, ) require.NoError(t, err) - _, err = client.GetPluginInfo(context.Background()) + _, err = client.GetPluginInfo(t.Context()) pluginrpcError := &pluginrpc.Error{} require.Error(t, err) require.ErrorAs(t, err, &pluginrpcError) diff --git a/check/internal/example/cmd/buf-plugin-field-lower-snake-case/main.go b/check/internal/example/cmd/buf-plugin-field-lower-snake-case/main.go index e52f3d2..40c7d5c 100644 --- a/check/internal/example/cmd/buf-plugin-field-lower-snake-case/main.go +++ b/check/internal/example/cmd/buf-plugin-field-lower-snake-case/main.go @@ -108,7 +108,7 @@ func toSnakeCase(s string) string { ((i < len(s)-1 && !isSnakeCaseNewWord(rune(s[i+1]), true) && !isDelimiter(rune(s[i+1]))) || (unicode.IsLower(rune(s[i-1])))): output += "_" + string(c) - case !(isDelimiter(c) && output[len(output)-1] == '_'): + case !isDelimiter(c) || output[len(output)-1] != '_': output += string(c) } } diff --git a/check/request.go b/check/request.go index 26c4161..92ee1f0 100644 --- a/check/request.go +++ b/check/request.go @@ -89,7 +89,7 @@ func WithAgainstFileDescriptors(againstFileDescriptors []descriptor.FileDescript } } -// WithOption adds the given Options to the Request. +// WithOptions adds the given Options to the Request. func WithOptions(options option.Options) RequestOption { return func(requestOptions *requestOptions) { requestOptions.options = options @@ -204,10 +204,7 @@ func (r *request) toProtos() ([]*checkv1.CheckRequest, error) { var checkRequests []*checkv1.CheckRequest for i := 0; i < len(r.ruleIDs); i += checkRuleIDPageSize { start := i - end := start + checkRuleIDPageSize - if end > len(r.ruleIDs) { - end = len(r.ruleIDs) - } + end := min(start+checkRuleIDPageSize, len(r.ruleIDs)) checkRequests = append( checkRequests, &checkv1.CheckRequest{ diff --git a/check/rule.go b/check/rule.go index a07b6b8..71097f5 100644 --- a/check/rule.go +++ b/check/rule.go @@ -35,19 +35,19 @@ type Rule interface { // // This uniquely identifies the Rule. ID() string - // The categories that the Rule is a part of. + // Categories that the Rule is a part of. // // Optional. // // Buf uses categories to include or exclude sets of rules via configuration. Categories() []Category - // Whether or not the Rule is a default Rule. + // Default defines whether the Rule is a default Rule. // // If a Rule is a default Rule, it will be called if a Request specifies no specific Rule IDs. // // A deprecated rule cannot be a default rule. Default() bool - // A user-displayable purpose of the rule. + // Purpose is a user-displayable purpose of the rule. // // Always present. // diff --git a/check/server.go b/check/server.go index 69d763e..0967587 100644 --- a/check/server.go +++ b/check/server.go @@ -18,6 +18,7 @@ import ( "buf.build/go/bufplugin/info" checkv1pluginrpc "buf.build/go/bufplugin/internal/gen/buf/plugin/check/v1/v1pluginrpc" infov1pluginrpc "buf.build/go/bufplugin/internal/gen/buf/plugin/info/v1/v1pluginrpc" + "buf.build/go/protovalidate" "pluginrpc.com/pluginrpc" ) @@ -36,13 +37,20 @@ func NewServer(spec *Spec, options ...ServerOption) (pluginrpc.Server, error) { option(serverOptions) } - checkServiceHandler, err := NewCheckServiceHandler(spec, CheckServiceHandlerWithParallelism(serverOptions.parallelism)) + checkServiceHandler, err := NewCheckServiceHandler( + spec, + CheckServiceHandlerWithParallelism(serverOptions.parallelism), + CheckServiceHandlerWithValidator(serverOptions.validator), + ) if err != nil { return nil, err } var pluginInfoServiceHandler infov1pluginrpc.PluginInfoServiceHandler if spec.Info != nil { - pluginInfoServiceHandler, err = info.NewPluginInfoServiceHandler(spec.Info) + pluginInfoServiceHandler, err = info.NewPluginInfoServiceHandler( + spec.Info, + info.PluginInfoHandlerWithValidator(serverOptions.validator), + ) if err != nil { return nil, err } @@ -113,8 +121,17 @@ func ServerWithParallelism(parallelism int) ServerOption { } } +// ServerWithValidator allows overriding the default validator used by the handler. +// By default, [protovalidate.GlobalValidator] is used. +func ServerWithValidator(validator protovalidate.Validator) ServerOption { + return func(options *serverOptions) { + options.validator = validator + } +} + type serverOptions struct { parallelism int + validator protovalidate.Validator } func newServerOptions() *serverOptions { diff --git a/go.mod b/go.mod index 5d56505..f0a1245 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,32 @@ module buf.build/go/bufplugin -go 1.23.0 - -toolchain go1.23.5 +go 1.25.0 require ( - buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.3-20250121211742-6d880cc6cc8d.1 - buf.build/go/protovalidate v0.12.0 + buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20250718181942-e35f9b667443.1 + buf.build/go/protovalidate v1.1.3 buf.build/go/spdx v0.2.0 github.com/bufbuild/protocompile v0.14.1 - github.com/stretchr/testify v1.10.0 - google.golang.org/protobuf v1.36.6 + github.com/stretchr/testify v1.11.1 + google.golang.org/protobuf v1.36.11 pluginrpc.com/pluginrpc v0.5.0 ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 // indirect - buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.3-20241007202033-cf42259fcbfc.1 // indirect - cel.dev/expr v0.23.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 // indirect + buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1 // indirect + cel.dev/expr v0.25.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/google/cel-go v0.25.0 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stoewer/go-strcase v1.3.0 // indirect - golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.23.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect + golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.28.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f5fb1c4..dee4948 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,27 @@ -buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.3-20250121211742-6d880cc6cc8d.1 h1:1v+ez1GRKKKdI1IwDDQqV98lGKo8489+Ekql+prUW6c= -buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.3-20250121211742-6d880cc6cc8d.1/go.mod h1:MYDFm9IHRP085R5Bis68mLc0mIqp5Q27Uk4o8YXjkAI= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 h1:YhMSc48s25kr7kv31Z8vf7sPUIq5YJva9z1mn/hAt0M= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= -buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.3-20241007202033-cf42259fcbfc.1 h1:NOipq02MS20WQCr6rfAG1o0n2AuQnY4Xg9avLl16csA= -buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.3-20241007202033-cf42259fcbfc.1/go.mod h1:jceo5esD5zSbflHHGad57RXzBpRrcPaiLrLQRA+Mbec= -buf.build/go/protovalidate v0.12.0 h1:4GKJotbspQjRCcqZMGVSuC8SjwZ/FmgtSuKDpKUTZew= -buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0= +buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20250718181942-e35f9b667443.1 h1:zQ9C3e6FtwSZUFuKAQfpIKGFk5ZuRoGt5g35Bix55sI= +buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20250718181942-e35f9b667443.1/go.mod h1:1Znr6gmYBhbxWUPRrrVnSLXQsz8bvFVw1HHJq2bI3VQ= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 h1:PMmTMyvHScV9Mn8wc6ASge9uRcHy0jtqPd+fM35LmsQ= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= +buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1 h1:iGPvEJltOXUMANWf0zajcRcbiOXLD90ZwPUFvbcuv6Q= +buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1/go.mod h1:nWVKKRA29zdt4uvkjka3i/y4mkrswyWwiu0TbdX0zts= +buf.build/go/protovalidate v1.1.3 h1:m2GVEgQWd7rk+vIoAZ+f0ygGjvQTuqPQapBBdcpWVPE= +buf.build/go/protovalidate v1.1.3/go.mod h1:9XIuohWz+kj+9JVn3WQneHA5LZP50mjvneZMnbLkiIE= buf.build/go/spdx v0.2.0 h1:IItqM0/cMxvFJJumcBuP8NrsIzMs/UYjp/6WSpq8LTw= buf.build/go/spdx v0.2.0/go.mod h1:bXdwQFem9Si3nsbNy8aJKGPoaPi5DKwdeEp5/ArZ6w8= -cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= -cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY= -github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -29,37 +30,32 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= -golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= +golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 h1:A2ni10G3UlplFrWdCDJTl7D7mJ7GSRm37S+PDimaKRw= -google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a h1:DMCgtIAIQGZqJXMVzJF4MV8BlWoJh2ZuFiRdAleyr58= +google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= pluginrpc.com/pluginrpc v0.5.0 h1:tOQj2D35hOmvHyPu8e7ohW2/QvAnEtKscy2IJYWQ2yo= diff --git a/info/plugin_info_service_handler.go b/info/plugin_info_service_handler.go index caf2760..01b7039 100644 --- a/info/plugin_info_service_handler.go +++ b/info/plugin_info_service_handler.go @@ -32,13 +32,25 @@ func NewPluginInfoServiceHandler(spec *Spec, options ...PluginInfoServiceHandler // PluginInfoServiceHandlerOption is an option for PluginInfoServiceHandler. type PluginInfoServiceHandlerOption func(*pluginInfoServiceHandlerOptions) +// PluginInfoHandlerWithValidator allows overriding the default validator used by the handler. +// By default, [protovalidate.GlobalValidator] is used. +func PluginInfoHandlerWithValidator(validator protovalidate.Validator) PluginInfoServiceHandlerOption { + return func(options *pluginInfoServiceHandlerOptions) { + options.validator = validator + } +} + // *** PRIVATE *** type pluginInfoServiceHandler struct { getPluginInfoResponse *infov1.GetPluginInfoResponse } -func newPluginInfoServiceHandler(spec *Spec, _ ...PluginInfoServiceHandlerOption) (*pluginInfoServiceHandler, error) { +func newPluginInfoServiceHandler(spec *Spec, options ...PluginInfoServiceHandlerOption) (*pluginInfoServiceHandler, error) { + opts := newPluginInfoServiceHandlerOptions() + for _, opt := range options { + opt(opts) + } // Also calls ValidateSpec. pluginInfo, err := NewPluginInfoForSpec(spec) if err != nil { @@ -48,9 +60,9 @@ func newPluginInfoServiceHandler(spec *Spec, _ ...PluginInfoServiceHandlerOption getPluginInfoResponse := &infov1.GetPluginInfoResponse{ PluginInfo: protoPluginInfo, } - validator, err := protovalidate.New() - if err != nil { - return nil, err + validator := opts.validator + if validator == nil { + validator = protovalidate.GlobalValidator } if err := validator.Validate(getPluginInfoResponse); err != nil { return nil, err @@ -64,4 +76,10 @@ func (c *pluginInfoServiceHandler) GetPluginInfo(context.Context, *infov1.GetPlu return c.getPluginInfoResponse, nil } -type pluginInfoServiceHandlerOptions struct{} +type pluginInfoServiceHandlerOptions struct { + validator protovalidate.Validator +} + +func newPluginInfoServiceHandlerOptions() *pluginInfoServiceHandlerOptions { + return &pluginInfoServiceHandlerOptions{} +} diff --git a/info/plugin_info_service_handler_test.go b/info/plugin_info_service_handler_test.go index 9cbf84f..6ada57d 100644 --- a/info/plugin_info_service_handler_test.go +++ b/info/plugin_info_service_handler_test.go @@ -15,7 +15,6 @@ package info import ( - "context" "testing" infov1 "buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go/buf/plugin/info/v1" @@ -33,7 +32,7 @@ func TestPluginInfoServiceHandlerBasic(t *testing.T) { require.NoError(t, err) _, err = pluginInfoServiceHandler.GetPluginInfo( - context.Background(), + t.Context(), &infov1.GetPluginInfoRequest{}, ) require.NoError(t, err) diff --git a/internal/pkg/cache/singleton_test.go b/internal/pkg/cache/singleton_test.go index b2aa8de..f7ba0af 100644 --- a/internal/pkg/cache/singleton_test.go +++ b/internal/pkg/cache/singleton_test.go @@ -25,7 +25,7 @@ import ( func TestBasic(t *testing.T) { t.Parallel() - ctx := context.Background() + ctx := t.Context() var count int singleton := NewSingleton( diff --git a/internal/pkg/thread/thread.go b/internal/pkg/thread/thread.go index ba3aa95..be26da7 100644 --- a/internal/pkg/thread/thread.go +++ b/internal/pkg/thread/thread.go @@ -72,8 +72,7 @@ func Parallelize(ctx context.Context, jobs []func(context.Context) error, option retErr = errors.Join(retErr, ctx.Err()) default: job := job - wg.Add(1) - go func() { + wg.Go(func() { if err := job(ctx); err != nil { lock.Lock() retErr = errors.Join(retErr, err) @@ -84,8 +83,7 @@ func Parallelize(ctx context.Context, jobs []func(context.Context) error, option } // This will never block. <-semaphoreC - wg.Done() - }() + }) } } } diff --git a/internal/pkg/thread/thread_test.go b/internal/pkg/thread/thread_test.go index d4c77a9..accdec7 100644 --- a/internal/pkg/thread/thread_test.go +++ b/internal/pkg/thread/thread_test.go @@ -40,7 +40,7 @@ func TestParallelizeSimple(t *testing.T) { }, ) } - ctx := context.Background() + ctx := t.Context() assert.NoError(t, Parallelize(ctx, jobs)) assert.Equal(t, int64(numJobs), executed.Load()) } @@ -60,7 +60,7 @@ func TestParallelizeImmediateCancellation(t *testing.T) { }, ) } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) cancel() assert.Error(t, Parallelize(ctx, jobs)) assert.Equal(t, int64(0), executed.Load()) diff --git a/option/errors.go b/option/errors.go index 7f5971e..15c76ad 100644 --- a/option/errors.go +++ b/option/errors.go @@ -40,6 +40,6 @@ func (u *unexpectedOptionValueTypeError) Error() string { var sb strings.Builder _, _ = sb.WriteString(`unexpected type for option value "`) _, _ = sb.WriteString(u.key) - _, _ = sb.WriteString(fmt.Sprintf(`": expected %T, got %T`, u.expected, u.actual)) + _, _ = fmt.Fprintf(&sb, `": expected %T, got %T`, u.expected, u.actual) return sb.String() } From f3d3f55e5f6888942ce0f7dd84ca80539d5ac1e0 Mon Sep 17 00:00:00 2001 From: "Philip K. Warren" Date: Wed, 15 Apr 2026 12:26:09 -0500 Subject: [PATCH 2/2] Update a few things from review --- check/server.go | 16 +++++++++++----- info/plugin_info_service_handler.go | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/check/server.go b/check/server.go index 0967587..e81f3b8 100644 --- a/check/server.go +++ b/check/server.go @@ -37,19 +37,25 @@ func NewServer(spec *Spec, options ...ServerOption) (pluginrpc.Server, error) { option(serverOptions) } - checkServiceHandler, err := NewCheckServiceHandler( - spec, + checkServiceHandlerOptions := []CheckServiceHandlerOption{ CheckServiceHandlerWithParallelism(serverOptions.parallelism), - CheckServiceHandlerWithValidator(serverOptions.validator), - ) + } + if serverOptions.validator != nil { + checkServiceHandlerOptions = append(checkServiceHandlerOptions, CheckServiceHandlerWithValidator(serverOptions.validator)) + } + checkServiceHandler, err := NewCheckServiceHandler(spec, checkServiceHandlerOptions...) if err != nil { return nil, err } var pluginInfoServiceHandler infov1pluginrpc.PluginInfoServiceHandler if spec.Info != nil { + var pluginInfoOptions []info.PluginInfoServiceHandlerOption + if serverOptions.validator != nil { + pluginInfoOptions = append(pluginInfoOptions, info.PluginInfoServiceHandlerWithValidator(serverOptions.validator)) + } pluginInfoServiceHandler, err = info.NewPluginInfoServiceHandler( spec.Info, - info.PluginInfoHandlerWithValidator(serverOptions.validator), + pluginInfoOptions..., ) if err != nil { return nil, err diff --git a/info/plugin_info_service_handler.go b/info/plugin_info_service_handler.go index 01b7039..8e4d53d 100644 --- a/info/plugin_info_service_handler.go +++ b/info/plugin_info_service_handler.go @@ -32,9 +32,9 @@ func NewPluginInfoServiceHandler(spec *Spec, options ...PluginInfoServiceHandler // PluginInfoServiceHandlerOption is an option for PluginInfoServiceHandler. type PluginInfoServiceHandlerOption func(*pluginInfoServiceHandlerOptions) -// PluginInfoHandlerWithValidator allows overriding the default validator used by the handler. +// PluginInfoServiceHandlerWithValidator allows overriding the default validator used by the handler. // By default, [protovalidate.GlobalValidator] is used. -func PluginInfoHandlerWithValidator(validator protovalidate.Validator) PluginInfoServiceHandlerOption { +func PluginInfoServiceHandlerWithValidator(validator protovalidate.Validator) PluginInfoServiceHandlerOption { return func(options *pluginInfoServiceHandlerOptions) { options.validator = validator }