1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-25 21:52:21 +00:00
2018-12-15 00:26:58 +08:00

133 lines
4.1 KiB
Markdown
Executable File

# Golang ProtoBuf Validator Compiler
[![Travis Build](https://travis-ci.org/mwitkow/go-proto-validators.svg)](https://travis-ci.org/mwitkow/go-proto-validators)
[![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](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.