Skip to content

Commit

Permalink
Add a global validate function
Browse files Browse the repository at this point in the history
  • Loading branch information
bufdev committed Oct 2, 2024
1 parent b477f4c commit 5ec9a4a
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 19 deletions.
32 changes: 13 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ message Transaction {
uint64 id = 1 [(buf.validate.field).uint64.gt = 999];
google.protobuf.Timestamp purchase_date = 2;
google.protobuf.Timestamp delivery_date = 3;
string price = 4 [(buf.validate.field).cel = {
id: "transaction.price",
message: "price must be positive and include a valid currency symbol ($ or £)",
Expand All @@ -94,7 +94,7 @@ message Transaction {
`protovalidate-go` assumes the constraint extensions are imported into
the generated code via `buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go`.

If you are using Buf [managed mode](https://buf.build/docs/generate/managed-mode/) to augment Go code generation, ensure
If you are using Buf [managed mode](https://buf.build/docs/generate/managed-mode/) to augment Go code generation, ensure
that the `protovalidate` module is excluded in your [`buf.gen.yaml`](https://buf.build/docs/configuration/v1/buf-gen-yaml#except):

**`buf.gen.yaml` v1**
Expand Down Expand Up @@ -129,7 +129,7 @@ package main
import (
"fmt"
"time"

pb "github.com/path/to/generated/protos"
"github.com/bufbuild/protovalidate-go"
"google.golang.org/protobuf/types/known/timestamppb"
Expand All @@ -142,13 +142,7 @@ func main() {
PurchaseDate: timestamppb.New(time.Now()),
DeliveryDate: timestamppb.New(time.Now().Add(time.Hour)),
}

v, err := protovalidate.New()
if err != nil {
fmt.Println("failed to initialize validator:", err)
}

if err = v.Validate(msg); err != nil {
if err = protovalidate.Validate(msg); err != nil {
fmt.Println("validation failed:", err)
} else {
fmt.Println("validation succeeded")
Expand All @@ -158,16 +152,16 @@ func main() {

### Lazy mode

`protovalidate-go` defaults to lazily construct validation logic for Protobuf
message types the first time they are encountered. A validator's internal
cache can be pre-warmed with the `WithMessages` or `WithDescriptors` options
`protovalidate-go` defaults to lazily construct validation logic for Protobuf
message types the first time they are encountered. A validator's internal
cache can be pre-warmed with the `WithMessages` or `WithDescriptors` options
during initialization:

```go
validator, err := protovalidate.New(
protovalidate.WithMessages(
&pb.MyFoo{},
&pb.MyBar{},
&pb.MyFoo{},
&pb.MyBar{},
),
)
```
Expand All @@ -191,7 +185,7 @@ validator, err := protovalidate.New(
### Support legacy `protoc-gen-validate` constraints

The `protovalidate-go` module comes with a `legacy` package which adds opt-in support
for existing `protoc-gen-validate` constraints. Provide the`legacy.WithLegacySupport`
for existing `protoc-gen-validate` constraints. Provide the`legacy.WithLegacySupport`
option when initializing the validator:

```go
Expand All @@ -200,16 +194,16 @@ validator, err := protovalidate.New(
)
```

`protoc-gen-validate` code generation is **not** used by `protovalidate-go`. The
`protoc-gen-validate` code generation is **not** used by `protovalidate-go`. The
`legacy` package assumes the `protoc-gen-validate` extensions are imported into
the generated code via `github.com/envoyproxy/protoc-gen-validate/validate`.

A [migration tool](https://github.com/bufbuild/protovalidate/tree/main/tools/protovalidate-migrate) is also available to incrementally upgrade legacy constraints in `.proto` files.

## Performance

[Benchmarks](validator_bench_test.go) are provided to test a variety of use-cases. Generally, after the
initial cold start, validation on a message is sub-microsecond
[Benchmarks](validator_bench_test.go) are provided to test a variety of use-cases. Generally, after the
initial cold start, validation on a message is sub-microsecond
and only allocates in the event of a validation error.

```
Expand Down
15 changes: 15 additions & 0 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package protovalidate

import (
"fmt"
"sync"

"buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
"github.com/bufbuild/protovalidate-go/celext"
Expand All @@ -27,6 +28,8 @@ import (
"google.golang.org/protobuf/reflect/protoregistry"
)

var getGlobalValidator = sync.OnceValues(func() (*Validator, error) { return New() })

Check failure on line 31 in validator.go

View workflow job for this annotation

GitHub Actions / Go (1.23.x)

getGlobalValidator is a global variable (gochecknoglobals)

type (
// A ValidationError is returned if one or more constraints on a message are
// violated. This error type can be converted into a validate.Violations
Expand Down Expand Up @@ -104,6 +107,18 @@ func (v *Validator) Validate(msg proto.Message) error {
return eval.EvaluateMessage(refl, v.failFast)
}

// Validate uses a global instance of Validator constructed with no ValidatorOptions and
// calls its Validate function. For the vast majority of validation cases, using this global
// function is safe and acceptable. If you need to provide i.e. a custom
// ExtensionTypeResolver, you'll need to construct a Validator.
func Validate(msg proto.Message) error {
globalValidator, err := getGlobalValidator()
if err != nil {
return err
}
return globalValidator.Validate(msg)
}

type config struct {
failFast bool
useUTC bool
Expand Down
31 changes: 31 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,37 @@ func TestValidator_Validate(t *testing.T) {
})
}

func TestValidator_ValidateGlobal(t *testing.T) {
t.Parallel()

t.Run("HasMsgExprs", func(t *testing.T) {
t.Parallel()

tests := []struct {
msg *pb.HasMsgExprs
exErr bool
}{
{
&pb.HasMsgExprs{X: 2, Y: 43},
false,
},
{
&pb.HasMsgExprs{X: 9, Y: 8},
true,
},
}

for _, test := range tests {
err := Validate(test.msg)
if test.exErr {
assert.Error(t, err)
} else {
require.NoError(t, err)
}
}
})
}

func TestRecursive(t *testing.T) {
t.Parallel()
val, err := New()
Expand Down

0 comments on commit 5ec9a4a

Please sign in to comment.