

Disclaimer: This post includes Amazon affiliate links. Clicking on them earns me a commission and does not affect the final price.
Historically, proto-gen-validate
has been the generator used to create validation rules for Protocol Buffers. However, thanks to the Buf team, a new imminent version is coming soon: V2, officially called protovalidate
, and an official beta is available for testing.
In this post, I will cover this new beta and show you how to integrate it with an existing protocol buffers codebase.
The code used for this post is available on GitHub.
protovalidate
?protovalidate
is a new generator that uses Google’s Common Expression Language (CEL) to define validation rules for the fields in your Protocol Buffer messages. It could be overwhelming initially, considering this is another thing you must learn, so take your time and read the docs before moving forward. I encourage you to start from the beginning by reading the protovalidate
docs.
For this example, I’m using the existing gRPC Microservice demo I used before; specifically, we are adding validation to some of the fields of the User message.
To start using protovalidate
, we need a few steps. Let’s get started.
buf.yaml
The first step is to update buf.yaml
to include the protovalidate
dependency. To do that, we edit it and add the following:
--- a/buf.yaml
+++ b/buf.yaml
@@ -5,3 +5,5 @@ breaking:
lint:
use:
- DEFAULT
+deps:
+ - buf.build/bufbuild/protovalidate
Next, we run buf mod update
, thus creating a new buf.lock
file, this new file includes the module’s dependency manifest: protovalidate
.
The next step is to update buf.gen.yaml
to properly generate the Go code after running buf generate. To do that, we add the following:
--- a/buf.gen.yaml
+++ b/buf.gen.yaml
@@ -2,6 +2,10 @@
version: v1
managed:
enabled: true
+ go_package_prefix:
+ default: .
+ except:
+ - buf.build/bufbuild/protovalidate
plugins:
- name: go # Synonym with: protoc-gen-<name>
out: gen/go
Adding the go_package_prefix
block will require us to update all .proto
files and remove the option go_package
directive. We need this to allow buf
to work properly and generate the Go files correctly.
Then we go and edit the User
message:
--- a/user/v1/user.proto
+++ b/user/v1/user.proto
@@ -2,13 +2,13 @@ syntax = "proto3";
package user.v1;
-option go_package = "github.com/MarioCarrion/grpc-microservice-example/gen/go/user/v1;userpb";
+import "buf/validate/validate.proto";
message User {
string uuid = 1;
- string full_name = 2;
- int64 birth_year = 3;
- optional uint32 salary = 4;
+ string full_name = 2 [(buf.validate.field).string.min_len = 1];
+ int64 birth_year = 3 [(buf.validate.field).int64.gt = 1900];
+ optional uint32 salary = 4 [(buf.validate.field).uint32.gt = 0];
repeated Address addresses = 5;
MaritalStatus marital_status = 6;
}
The two essential things to call out are the import of validate.proto
and the constraint directives added to validate some of the fields:
full_name
has to be at least 1,birth_year
must be greater than 1900, andsalary
field has to be greater than 0.Finally, run buf generate
to generate the new Go code that supports validation.
For this final step, we need to import the package bufbuild/protovalidate-go
:
go get github.com/bufbuild/protovalidate-go
Then, implement the validation code to validate our message effectively:
--- /dev/null
+++ b/examples/validate/main.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/bufbuild/protovalidate-go"
+
+ userpb "github.com/MarioCarrion/grpc-microservice-example/gen/go/user/v1"
+)
+
+func main() {
+ user := &userpb.User{}
+
+ v, err := protovalidate.New()
+ if err != nil {
+ fmt.Println("failed to initialize validator:", err)
+ }
+
+ if err = v.Validate(user); err != nil {
+ fmt.Println("validation failed:", err)
+ } else {
+ fmt.Println("validation succeeded")
+ }
+}
Using protovalidate
is an exciting approach to validating Protocol Buffers. Things get interesting when trying to determine precisely the error. Still, other than that, this is an excellent way to explicitly indicate to your customers the expected behavior of the messages in a standard mechanism that applies to any programming language.
If you’re looking to sink your teeth into more Go-related topics I recommend the following: