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.
What is 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.
Using protovalidate
To start using protovalidate, we need a few steps. Let’s get started.
Update 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.
Update messages
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:
- The length of
full_namehas to be at least 1, birth_yearmust be greater than 1900, and- The optional
salaryfield has to be greater than 0.
Finally, run buf generate to generate the new Go code that supports validation.
Putting everything together
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")
+ }
+}
Conclusion
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.
Recommended reading
If you’re looking to sink your teeth into more Go-related topics I recommend the following:

