mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-25 21:52:21 +00:00
133 lines
4.1 KiB
Markdown
Executable File
133 lines
4.1 KiB
Markdown
Executable File
# Golang ProtoBuf Validator Compiler
|
|
|
|
[](https://travis-ci.org/mwitkow/go-proto-validators)
|
|
[](LICENSE)
|
|
|
|
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.
|
|
|
|
## Paint me a code picture
|
|
|
|
Let's take the following `proto3` snippet:
|
|
|
|
```proto
|
|
syntax = "proto3";
|
|
package validator.examples;
|
|
import "github.com/mwitkow/go-proto-validators/validator.proto";
|
|
|
|
message InnerMessage {
|
|
// some_integer can only be in range (1, 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-z]{2,5}$"}];
|
|
// 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:
|
|
|
|
```go
|
|
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-z]{2,5}$")
|
|
|
|
func (this *OuterMessage) Validate() error {
|
|
if !_regex_OuterMessage_ImportantString.MatchString(this.ImportantString) {
|
|
return fmt.Errorf("validation error: OuterMessage.ImportantString must conform to regex '^[a-z]{2,5}$'")
|
|
}
|
|
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
|
|
}
|
|
```
|
|
|
|
## Installing and using
|
|
|
|
The `protoc` compiler expects to find plugins named `proto-gen-XYZ` on the execution `$PATH`. So first:
|
|
|
|
```sh
|
|
export PATH=${PATH}:${GOPATH}/bin
|
|
```
|
|
|
|
Then, do the usual
|
|
|
|
```sh
|
|
go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators
|
|
```
|
|
|
|
Your `protoc` builds probably look very simple like:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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 :)
|
|
|
|
###License
|
|
|
|
`go-proto-validators` is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.
|
|
|
|
|
|
|