GitHub - envoyproxy/protoc-gen-validate: protoc plugin to generate polyglot message validators
PGV可以指定语言生成校验,readme也给出了规则的写法
protoc --proto_path=./ --cpp_out=./rex_test ./test2.proto --validate_out="lang=cc:./"
proto文件需要import validate/validator.proto,才可以使用pgv的校验规则
使用pgv,可以在proto文件中对字段定义校验规则,他会对每个message对象都执行校验,在接口代码中,我们只需要include这个头文件,使用校验函数即可
syntax = "proto3";
package examplepb;
import "validate/validate.proto";
message Person {
uint64 id = 1 [(validate.rules).uint64.gt = 999];
string email = 2 [(validate.rules).string.email = true];
string name = 3 [(validate.rules).string = {
pattern: "^[^[0-9]A-Za-z]+( [^[0-9]A-Za-z]+)*$",
max_bytes: 256,
}];
Location home = 4 [(validate.rules).message.required = true];
message Location {
double lat = 1 [(validate.rules).double = { gte: -90, lte: 90 }];
double lng = 2 [(validate.rules).double = { gte: -180, lte: 180 }];
}
}
上面这个proto文件,pgv生成的桩代码为
// Code generated by protoc-gen-validate
// source: test2.proto
// DO NOT EDIT!!!
#include "test2.pb.validate.h"
#include <google/protobuf/message.h>
#include <google/protobuf/util/time_util.h>
#include "re2/re2.h"
namespace pgv {
namespace protobuf = google::protobuf;
namespace protobuf_wkt = google::protobuf;
namespace validate {
using std::string;
// define the regex for a UUID once up-front
const re2::RE2 _uuidPattern(
"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{"
"12}$");
pgv::Validator<::examplepb::Person> validator___examplepb__Person(
static_cast<bool (*)(const ::examplepb::Person&, pgv::ValidationMsg*)>(
::examplepb::Validate));
pgv::Validator<::examplepb::Person_Location>
validator___examplepb__Person_Location(
static_cast<bool (*)(const ::examplepb::Person_Location&,
pgv::ValidationMsg*)>(::examplepb::Validate));
} // namespace validate
} // namespace pgv
namespace examplepb {
// Validate checks the field values on ::examplepb::Person with the rules
// defined in the proto definition for this message. If any rules are
// violated, the return value is false and an error message is written to the
// input string argument.
const re2::RE2 _Person_Name_Pattern(
re2::StringPiece("^[^[0-9]A-Za-z]+( [^[0-9]A-Za-z]+)*$",
sizeof("^[^[0-9]A-Za-z]+( [^[0-9]A-Za-z]+)*$") - 1));
bool Validate(const ::examplepb::Person& m, pgv::ValidationMsg* err) {
(void)m;
(void)err;
if (m.id() <= 999) {
{
std::ostringstream msg("invalid ");
msg << "PersonValidationError"
<< "."
<< "Id";
msg << ": "
<< "value must be greater than 999";
*err = msg.str();
return false;
}
}
throw pgv::UnimplementedException(
"C++ email address validation is not implemented");
if (m.name().size() > 256) {
{
std::ostringstream msg("invalid ");
msg << "PersonValidationError"
<< "."
<< "Name";
msg << ": "
<< "value length must be at most 256 bytes";
*err = msg.str();
return false;
}
}
{
if (!RE2::FullMatch(re2::StringPiece(m.name().c_str(), m.name().size()),
_Person_Name_Pattern)) {
{
std::ostringstream msg("invalid ");
msg << "PersonValidationError"
<< "."
<< "Name";
msg << ": "
<< "value does not match regex pattern \"^[^[0-9]A-Za-z]+( "
"[^[0-9]A-Za-z]+)*$\"";
*err = msg.str();
return false;
}
}
}
if (!m.has_home()) {
{
std::ostringstream msg("invalid ");
msg << "PersonValidationError"
<< "."
<< "Home";
msg << ": "
<< "value is required";
*err = msg.str();
return false;
}
}
{
pgv::ValidationMsg inner_err;
if (m.has_home() &&
!pgv::BaseValidator::AbstractCheckMessage(m.home(), &inner_err)) {
{
std::ostringstream msg("invalid ");
msg << "PersonValidationError"
<< "."
<< "Home";
msg << ": "
<< "embedded message failed validation";
msg << " | caused by " << inner_err;
*err = msg.str();
return false;
}
}
}
return true;
}
// Validate checks the field values on ::examplepb::Person_Location with the
// rules defined in the proto definition for this message. If any rules are
// violated, the return value is false and an error message is written to the
// input string argument.
bool Validate(const ::examplepb::Person_Location& m, pgv::ValidationMsg* err) {
(void)m;
(void)err;
if (m.lat() < -90 || m.lat() > 90) {
{
std::ostringstream msg("invalid ");
msg << "LocationValidationError"
<< "."
<< "Lat";
msg << ": "
<< "value must be inside range [-90, 90]";
*err = msg.str();
return false;
}
}
if (m.lng() < -180 || m.lng() > 180) {
{
std::ostringstream msg("invalid ");
msg << "LocationValidationError"
<< "."
<< "Lng";
msg << ": "
<< "value must be inside range [-180, 180]";
*err = msg.str();
return false;
}
}
return true;
}
} // namespace examplepb
生成的文件依赖有re2正则表达式库,以及依赖validate.h;前者基本上是内置的,后者是pgv生成的,所以编译时需要包含pgv源文件所在的validate目录,这个目录下才有这个头文件
上面的错误信息ValidationMsg,其实就是std::string,通过using声明的别名而已;
protoc-gen-validate/rule_comparison.md at main · envoyproxy/protoc-gen-validate · GitHub
pgv对于不同语言支持的情况
不同语言支持的规则特性不一样,所以上面有一个throw异常的情况,是因为c++没有实现这部分规则
go语言下的protoc编译命令,-I是告诉protoc,我们引用的validator.proto文件在哪里
protoc \
-I . \
-I ${GOPATH}/src \
-I ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate \
--go_out=":../generated" \
--validate_out="lang=go:../generated" \
example.proto
安装方法
# fetches this repo into $GOPATH
go get -d github.com/envoyproxy/protoc-gen-validate
# installs PGV into $GOPATH/bin
make build
# 或者
git clone https://github.com/envoyproxy/protoc-gen-validate.git
cd protoc-gen-validate/
make build
# 源代码自带protoc-gen-go二进制文件
# 如果没有go环境
# make时可以临时export GOPATH=/your/dir/protoc-gen-validate/
可以在Makefile中生成
.PHONY: validate
# generate validate proto
validate:
protoc --proto_path=. \
--cpp_out=./rex_test
--validate_out="lang=cc:./"
$(API_PROTO_FILES)
#protoc --proto_path=. \
# --proto_path=./third_party \
# --go_out=paths=source_relative:. \
# --validate_out=paths=source_relative,lang=go:. \
# $(API_PROTO_FILES)