A protoc
plugin that generates Validate() error
functions on Go proto struct
s based on field options inside .proto
files. The validation functions are code-generated and thus don't suffer on performance from tag-based reflection on
deeply-nested messages.
Using Protobuf validators is currently verified to work with:
- Go 1.11, 1.12, 1.13
- Protobuf @
v3.8.0
- Go Protobuf @
v1.3.2
- Gogo Protobuf @
v1.3.0
It should still be possible to use it in project using earlier Go versions. However if you want to contribute to this repository you'll need at least 1.11 for Go module support.
Let's take the following proto3
snippet:
syntax = "proto3";
package validator.examples;
import "github.com/qredo/go-proto-validators/validator.proto";
message InnerMessage {
// some_integer can only be in range (0, 100).
int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];
// some_float can only be in range (0;1).
double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
}
message OuterMessage {
// important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).
string important_string = 1 [(validator.field) = {regex: "^[a-z0-9]{5,30}$"}];
// proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.
InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
}
First, the required
keyword is back for proto3
, under the guise of msg_exists
. The painful if-nil
checks are taken care of!
Second, the expected values in fields are now part of the contract .proto
file. No more hunting down conditions in code!
Third, the generated code is understandable and has clear understandable error messages. Take a look:
func (this *InnerMessage) Validate() error {
if !(this.SomeInteger > 0) {
return fmt.Errorf("validation error: InnerMessage.SomeInteger must be greater than '0'")
}
if !(this.SomeInteger < 100) {
return fmt.Errorf("validation error: InnerMessage.SomeInteger must be less than '100'")
}
if !(this.SomeFloat >= 0) {
return fmt.Errorf("validation error: InnerMessage.SomeFloat must be greater than or equal to '0'")
}
if !(this.SomeFloat <= 1) {
return fmt.Errorf("validation error: InnerMessage.SomeFloat must be less than or equal to '1'")
}
return nil
}
var _regex_OuterMessage_ImportantString = regexp.MustCompile("^[a-z0-9]{5,30}$")
func (this *OuterMessage) Validate() error {
if !_regex_OuterMessage_ImportantString.MatchString(this.ImportantString) {
return fmt.Errorf("validation error: OuterMessage.ImportantString must conform to regex '^[a-z0-9]{5,30}$'")
}
if nil == this.Inner {
return fmt.Errorf("validation error: OuterMessage.Inner message must exist")
}
if this.Inner != nil {
if err := validators.CallValidatorIfExists(this.Inner); err != nil {
return err
}
}
return nil
}
The protoc
compiler expects to find plugins named proto-gen-XYZ
on the execution $PATH
. So first:
export PATH=${PATH}:${GOPATH}/bin
Then, do the usual
go get github.com/qredo/go-proto-validators/protoc-gen-govalidators
Your protoc
builds probably look very simple like:
protoc \
--proto_path=. \
--go_out=. \
*.proto
That's fine, until you encounter .proto
includes. Because go-proto-validators
uses field options inside the .proto
files themselves, it's .proto
definition (and the Google descriptor.proto
itself) need to on the protoc
include
path. Hence the above becomes:
protoc \
--proto_path=${GOPATH}/src \
--proto_path=${GOPATH}/src/github.com/google/protobuf/src \
--proto_path=. \
--go_out=. \
--govalidators_out=. \
*.proto
Or with gogo protobufs:
protoc \
--proto_path=${GOPATH}/src \
--proto_path=${GOPATH}/src/github.com/gogo/protobuf/protobuf \
--proto_path=. \
--gogo_out=. \
--govalidators_out=gogoimport=true:. \
*.proto
Basically the magical incantation (apart from includes) is the --govalidators_out
. That triggers the
protoc-gen-govalidators
plugin to generate mymessage.validator.pb.go
. That's it :)
go-proto-validators
is released under the Apache 2.0 license. See the LICENSE file for details.