bufbuild/protoc-gen-validate
GitHub: bufbuild/protoc-gen-validate
一个 protoc 插件,通过 proto 文件中的注解为 Protocol Buffers 消息自动生成多语言的字段值语义验证代码。
Stars: 4122 | Forks: 605
# [][buf] protoc-gen-validate (PGV)



PGV 是一个 protoc 插件,用于生成多语言 message 验证器。虽然 protocol
buffers 有效地保证了结构化数据的类型,但它们无法强制执行
值的语义规则。该插件为 protoc 生成的代码添加了支持,以
验证此类约束。
开发者导入 PGV 扩展,并在其 proto 文件中使用约束规则注释 message 和字段:
```
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: "^[A-Za-z]+( [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}];
}
}
```
使用 PGV 和目标语言的默认插件执行 `protoc`,将会
在生成的类型上创建 `Validate` 方法:
```
p := new(Person)
err := p.Validate() // err: Id must be greater than 999
p.Id = 1000
err = p.Validate() // err: Email must be a valid email address
p.Email = "example@bufbuild.com"
err = p.Validate() // err: Name must match pattern '^[A-Za-z]+( [A-Za-z]+)*$'
p.Name = "Protocol Buffer"
err = p.Validate() // err: Home is required
p.Home = &Location{37.7, 999}
err = p.Validate() // err: Home.Lng must be within [-180, 180]
p.Home.Lng = -122.4
err = p.Validate() // err: nil
```
## 用法
### 依赖项
- `go` 工具链 (≥ v1.7)
- `$PATH` 中的 `protoc` 编译器
- `$PATH` 中的 `protoc-gen-validate`
- 目标语言的官方特定语言插件
- **目前仅支持 `proto3` 语法。**
### 安装
#### 从 GitHub Releases 下载
从 [GitHub Releases](https://github.com/bufbuild/protoc-gen-validate/releases) 下载资源文件并解压,然后将插件添加到 `$PATH` 中。
#### 从源码构建
```
# 将此 repo 拉取到 $GOPATH
go get -d github.com/envoyproxy/protoc-gen-validate
```
```
git clone https://github.com/bufbuild/protoc-gen-validate.git
# 将 PGV 安装到 $GOPATH/bin
cd protoc-gen-validate && make build
```
### 参数
- **`lang`**: 指定要生成的目标语言。目前唯一
支持的选项有:
- `go`
- `cc` 用于 c++(部分实现)
- `java`
- 注意:Python 通过运行时代码生成工作。没有编译时
生成。详情请参阅 Python 部分。
### 示例
#### Go
Go 生成应输出到与官方插件相同的输出路径。对于
proto 文件 `example.proto`,相应的验证代码会生成
到 `../generated/example.pb.validate.go`:
```
protoc \
-I . \
-I path/to/validate/ \
--go_out=":../generated" \
--validate_out="lang=go:../generated" \
example.proto
```
所有生成的 message 都包含以下方法:
- `Validate() error` 返回验证期间遇到的第一个错误。
- `ValidateAll() error` 返回验证期间遇到的所有错误。
PGV 不需要现有生成代码之外的任何额外运行时
依赖。
**注意**:默认情况下,**example.pb.validate.go** 嵌套在与您的 `option go_package` 名称相匹配的目录结构中。您可以使用
protoc 参数 `paths=source_relative:.` 来更改此设置,例如 `--validate_out="lang=go,paths=source_relative:../generated"`。这样 `--validate_out` 就会将
文件输出到预期位置。有关
更多信息,请参阅 Google 的 protobuf 文档,或查看 [包和输入路径](https://github.com/golang/protobuf#packages-and-input-paths) 或 [参数](https://github.com/golang/protobuf#parameters)。
此外,还支持 [此处描述](https://developers.google.com/protocol-buffers/docs/reference/go-generated#invocation)
的 `module=example.com/foo` 标志。
对于较新的 Buf CLI 版本 (>v1.9.0),您可以使用新的 plugin 键,而不是直接使用 `protoc` 命令:
```
# buf.gen.yaml
version: v1
plugins:
- plugin: buf.build/bufbuild/validate-go
out: gen
```
```
# proto/buf.yaml
version: v1
deps:
- buf.build/envoyproxy/protoc-gen-validate
```
#### Java
Java 生成与现有的用于 java
项目的 protobuf 工具链集成。对于 Maven 项目,请
将以下内容添加到您的 pom.xml 或 build.gradle 中。
```
build.buf.protoc-gen-validate
pgv-java-stub
${pgv.version}
kr.motd.maven
os-maven-plugin
1.4.1.Final
org.xolstice.maven.plugins
protobuf-maven-plugin
0.6.1
com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
protoc-java-pgv
compile-custom
lang=java
java-pgv
build.buf.protoc-gen-validate:protoc-gen-validate:${pgv.version}:exe:${os.detected.classifier}
```
```
plugins {
...
id "com.google.protobuf" version "${protobuf.version}"
...
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:${protoc.version}"
}
plugins {
javapgv {
artifact = "build.buf.protoc-gen-validate:protoc-gen-validate:${pgv.version}"
}
}
generateProtoTasks {
all()*.plugins {
javapgv {
option "lang=java"
}
}
}
}
```
```
// Create a validator index that reflectively loads generated validators
ValidatorIndex index = new ReflectiveValidatorIndex();
// Assert that a message is valid
index.validatorFor(message.getClass()).assertValid(message);
// Create a gRPC client and server interceptor to automatically validate messages (requires pgv-java-grpc module)
clientStub = clientStub.withInterceptors(new ValidatingClientInterceptor(index));
serverBuilder.addService(ServerInterceptors.intercept(svc, new ValidatingServerInterceptor(index)));
```
#### Python
Python 实现通过 JIT 代码生成工作。换句话说,
`validate(msg)` 函数是按需编写并
[exec-ed](https://docs.python.org/3/library/functions.html#exec) 的。
LRU 缓存通过
为每个描述符存储生成的函数来提高性能。
Python 包可在
[PyPI](https://pypi.org/project/protoc-gen-validate) 上获取。
要运行 `validate()`,请执行以下操作:
```
from entities_pb2 import Person
from protoc_gen_validate.validator import validate, ValidationFailed
p = Person(first_name="Foo", last_name="Bar", age=42)
try:
validate(p)
except ValidationFailed as err:
print(err)
```
您可以使用 `print_validate()`
函数查看生成了什么代码。
## 约束规则
[提供的约束](validate/validate.proto) 在很大程度上仿照
JSON Schema 中的约束。PGV 规则可以对同一字段混合使用;该插件
可确保应用于字段的规则在代码生成前不会相互矛盾。
请查看 [约束规则比较矩阵](rule_comparison.md) 以了解
特定语言的约束功能。
### 数值类型
- **const**: 该字段必须_完全_等于指定的值。
// x 必须精确等于 1.23
float x = 1 [(validate.rules).float.const = 1.23];
- **lt/lte/gt/gte**: 这些不等式(分别对应 `<`、`<=`、`>`、`>=`)
允许推导字段必须驻留的范围。
// x 必须小于 10
int32 x = 1 [(validate.rules).int32.lt = 10];
// x 必须大于或等于 20
uint64 x = 1 [(validate.rules).uint64.gte = 20];
// x 必须在范围 [30, 40) 内
fixed32 x = 1 [(validate.rules).fixed32 = {gte:30, lt: 40}];
反转 `lt(e)` 和 `gt(e)` 的值是有效的,这会创建一个互斥
范围。
// x 必须在范围 [30, 40) 之外
double x = 1 [(validate.rules).double = {lt:30, gte:40}];
- **in/not_in**: 这两条规则允许为
字段的值指定允许/拒绝列表。
// x 必须是 1、2 或 3
uint32 x = 1 [(validate.rules).uint32 = {in: [1,2,3]}];
// x 不能是 0 也不能是 0.99
float x = 1 [(validate.rules).float = {not_in: [0, 0.99]}];
- **ignore_empty**: 此规则指定如果字段为空或设置为
默认值,则忽略任何验证规则。这通常在
需要在更新请求中取消设置字段,或者
对于切换到 WKTs 不可行的可选字段跳过验证时非常有用。
uint32 x = 1 [(validate.rules).uint32 = {ignore_empty: true, gte: 200}];
### 布尔值
- **const**: 该字段必须_完全_等于指定的值。
// x 必须设置为 true
bool x = 1 [(validate.rules).bool.const = true];
// x 不能设置为 true
bool x = 1 [(validate.rules).bool.const = false];
### 字符串
- **const**: 该字段必须_完全_等于指定的值。
// x 必须设置为 "foo"
string x = 1 [(validate.rules).string.const = "foo"];
- **len/min_len/max_len**: 这些规则限制字段中的字符数(
Unicode 码点)。请注意,字符数可能
与字符串中的字节数不同。字符串被视为原样,
不进行规范化。
// x 的长度必须正好是 5 个字符
string x = 1 [(validate.rules).string.len = 5];
// x 的长度必须至少为 3 个字符
string x = 1 [(validate.rules).string.min_len = 3];
// x 的长度必须在 5 到 10 个字符之间(包含边界)
string x = 1 [(validate.rules).string = {min_len: 5, max_len: 10}];
- **min_bytes/max_bytes**: 这些规则限制字段中的字节数。
// x 的长度最多为 15 字节
string x = 1 [(validate.rules).string.max_bytes = 15];
// x 的长度必须在 128 到 1024 字节之间
string x = 1 [(validate.rules).string = {min_bytes: 128, max_bytes: 1024}];
- **pattern**: 该字段必须匹配指定的[兼容 RE2][re2]正则
表达式。包含的表达式应省略任何分隔符(即,`/\d+/`
应直接写为 `\d+`)。
// x 必须是一个非空的、不区分大小写的十六进制字符串
string x = 1 [(validate.rules).string.pattern = "(?i)^[0-9a-f]+$"];
- **prefix/suffix/contains/not_contains**: 该字段必须在可选的明确位置包含指定的子字符串,或不包含指定的子字符串。
// x 必须以 "foo" 开头
string x = 1 [(validate.rules).string.prefix = "foo"];
// x 必须以 "bar" 结尾
string x = 1 [(validate.rules).string.suffix = "bar"];
// x 的任意位置必须包含 "baz"
string x = 1 [(validate.rules).string.contains = "baz"];
// x 的任意位置不能包含 "baz"
string x = 1 [(validate.rules).string.not_contains = "baz"];
// x 必须以 "fizz" 开头并以 "buzz" 结尾
string x = 1 [(validate.rules).string = {prefix: "fizz", suffix: "buzz"}];
// x 必须以 ".proto" 结尾且少于 64 个字符
string x = 1 [(validate.rules).string = {suffix: ".proto", max_len:64}];
- **in/not_in**: 这两条规则允许为字段的值指定允许/拒绝列表。
// x 必须是 "foo"、"bar" 或 "baz"
string x = 1 [(validate.rules).string = {in: ["foo", "bar", "baz"]}]
// x 不能是 "fizz" 也不能是 "buzz"
string x = 1 [(validate.rules).string = {not_in: ["fizz", "buzz"]}];
- **ignore_empty**: 此规则指定如果字段为空或设置为
默认值,则忽略任何验证规则。这通常在
需要在更新请求中取消设置字段,或者
对于切换到 WKTs 不可行的可选字段跳过验证时非常有用。
string CountryCode = 1 [(validate.rules).string = {ignore_empty: true, len: 2}];
- **well-known formats**: 这些规则为常见的
字符串模式提供高级约束。这些约束通常比
等效的正则表达式模式更宽松且
性能更高,同时提供更具
解释性的失败描述。
// x 必须是有效的电子邮件地址(通过 RFC 5322)
string x = 1 [(validate.rules).string.email = true];
// x 必须是有效地址(IP 或主机名)。
string x = 1 [(validate.rules).string.address = true];
// x 必须是有效的主机名(通过 RFC 1034)
string x = 1 [(validate.rules).string.hostname = true];
// x 必须是有效的 IP 地址(v4 或 v6)
string x = 1 [(validate.rules).string.ip = true];
// x 必须是有效的 IPv4 地址
// 例如:"192.168.0.1"
string x = 1 [(validate.rules).string.ipv4 = true];
// x 必须是有效的 IPv6 地址
// 例如:"fe80::3"
string x = 1 [(validate.rules).string.ipv6 = true];
// x 必须是有效的绝对 URI(通过 RFC 3986)
string x = 1 [(validate.rules).string.uri = true];
// x 必须是有效的 URI 引用(绝对或相对)
string x = 1 [(validate.rules).string.uri_ref = true];
// x 必须是有效的 UUID(通过 RFC 4122)
string x = 1 [(validate.rules).string.uuid = true];
// x 必须符合 HTTP 标头名称的知名正则表达式(通过 RFC 7230)
string x = 1 [(validate.rules).string.well_known_regex = HTTP_HEADER_NAME]
// x 必须符合 HTTP 标头值的知名正则表达式(通过 RFC 7230)
string x = 1 [(validate.rules).string.well_known_regex = HTTP_HEADER_VALUE];
// x 必须符合标头的知名正则表达式,不允许 \r\n\0 字符。
string x = 1 [(validate.rules).string {well_known_regex: HTTP_HEADER_VALUE, strict: false}];
### 字节
- **const**: 该字段必须_完全_等于指定的值。
// x 必须设置为 "foo" ("\x66\x6f\x6f")
bytes x = 1 [(validate.rules).bytes.const = "foo"];
// x 必须设置为 "\xf0\x90\x28\xbc"
bytes x = 1 [(validate.rules).bytes.const = "\xf0\x90\x28\xbc"];
- **len/min_len/max_len**: 这些规则限制字段中的字节数。
// x 必须正好是 3 字节
bytes x = 1 [(validate.rules).bytes.len = 3];
// x 必须至少为 3 字节长
bytes x = 1 [(validate.rules).bytes.min_len = 3];
// x 必须在 5 到 10 字节之间(包含边界)
bytes x = 1 [(validate.rules).bytes = {min_len: 5, max_len: 10}];
- **pattern**: 该字段必须匹配指定的[兼容 RE2][re2]正则
表达式。包含的表达式应省略任何分隔符(即,/\d+/`
应直接写为 `\d+`)。
// x 必须是非空的 ASCII 字节序列
bytes x = 1 [(validate.rules).bytes.pattern = "^[\x00-\x7F]+$"];
- **prefix/suffix/contains**: 该字段必须在可选的明确位置包含指定的字节序列。
// x 必须以 "\x99" 开头
bytes x = 1 [(validate.rules).bytes.prefix = "\x99"];
// x 必须以 "buz\x7a" 结尾
bytes x = 1 [(validate.rules).bytes.suffix = "buz\x7a"];
// x 的任意位置必须包含 "baz"
bytes x = 1 [(validate.rules).bytes.contains = "baz"];
- **in/not_in**: 这两条规则允许为字段的值指定允许/拒绝列表。
// x 必须是 "foo"、"bar" 或 "baz"
bytes x = 1 [(validate.rules).bytes = {in: ["foo", "bar", "baz"]}]
// x 不能是 "fizz" 也不能是 "buzz"
bytes x = 1 [(validate.rules).bytes = {not_in: ["fizz", "buzz"]}];
- **ignore_empty**: 此规则指定如果字段为空或设置为
默认值,则忽略任何验证规则。这通常在
需要在更新请求中取消设置字段,或者
对于切换到 WKTs 不可行的可选字段跳过验证时非常有用。
bytes x = 1 [(validate.rules).bytes = {ignore_empty: true, in: ["foo", "bar", "baz"]}];
- **well-known formats**: 这些规则为常见
模式提供高级约束。这些约束通常比
等效的正则表达式模式更宽松且
性能更高,同时提供更具
解释性的失败描述。
// x 必须是字节格式的有效 IP 地址(v4 或 v6)
bytes x = 1 [(validate.rules).bytes.ip = true];
// x 必须是字节格式的有效 IPv4 地址
// 例如:"\xC0\xA8\x00\x01"
bytes x = 1 [(validate.rules).bytes.ipv4 = true];
// x 必须是字节格式的有效 IPv6 地址
// 例如:"\x20\x01\x0D\xB8\x85\xA3\x00\x00\x00\x00\x8A\x2E\x03\x70\x73\x34"
bytes x = 1 [(validate.rules).bytes.ipv6 = true];
### 枚举
以下示例使用此 `State` 枚举
```
enum State {
INACTIVE = 0;
PENDING = 1;
ACTIVE = 2;
}
```
- **const**: 该字段必须_完全_等于指定的值。
// x 必须设置为 ACTIVE (2)
State x = 1 [(validate.rules).enum.const = 2];
- **defined_only**: 该字段必须是枚举
描述符中指定的值之一。
// x 只能是 INACTIVE、PENDING 或 ACTIVE
State x = 1 [(validate.rules).enum.defined_only = true];
- **in/not_in**: 这两条规则允许为字段的值指定允许/拒绝列表。
// x 必须是 INACTIVE (0) 或 ACTIVE (2)
State x = 1 [(validate.rules).enum = {in: [0,2]}];
// x 不能是 PENDING (1)
State x = 1 [(validate.rules).enum = {not_in: [1]}];
### Messages
```
// if Person was generated with PGV and x is set,
// x's fields will be validated.
Person x = 1;
```
- **skip**: 此规则指定不应
评估此字段的验证规则。
// Person x 上的字段将不会被验证。
Person x = 1 [(validate.rules).message.skip = true];
- **required**: 此规则指定不能取消设置该字段。
// x 不能被取消设置
Person x = 1 [(validate.rules).message.required = true];
// x 不能被取消设置,但不会执行对 x 的验证
Person x = 1 [(validate.rules).message = {required: true, skip: true}];
### Repeated
- **min_items/max_items**: 这些规则控制字段中包含
多少个元素
// x 必须包含至少 3 个元素
repeated int32 x = 1 [(validate.rules).repeated.min_items = 3];
// x 必须包含 5 到 10 个 Persons(包含边界)
repeated Person x = 1 [(validate.rules).repeated = {min_items: 5, max_items: 10}];
// x 必须正好包含 7 个元素
repeated double x = 1 [(validate.rules).repeated = {min_items: 7, max_items: 7}];
- **unique**: 此规则要求字段中的所有元素必须是唯一的。
此规则不支持 repeated message。
// x 必须包含唯一的 int64 值
repeated int64 x = 1 [(validate.rules).repeated.unique = true];
- **items**: 此规则指定应应用于字段中
每个元素的约束。除非在此约束上指定了 `skip`,否则 Repeated message 字段也会应用其验证规则。
// x 必须包含正浮点数值
repeated float x = 1 [(validate.rules).repeated.items.float.gt = 0];
// x 必须包含 Persons,但不验证它们
repeated Person x = 1 [(validate.rules).repeated.items.message.skip = true];
- **ignore_empty**: 此规则指定如果字段为空或设置为
默认值,则忽略任何验证规则。这通常在
需要在更新请求中取消设置字段,或者
对于切换到 WKTs 不可行的可选字段跳过验证时非常有用。
repeated int64 x = 1 [(validate.rules).repeated = {ignore_empty: true, items: {int64: {gt: 200}}}];
### Maps
- **min_pairs/max_pairs**: 这些规则控制此字段中包含
多少个 KV 对
// x 必须包含至少 3 个 KV 对
map x = 1 [(validate.rules).map.min_pairs = 3];
// x 必须包含 5 到 10 个 KV 对
map x = 1 [(validate.rules).map = {min_pairs: 5, max_pairs: 10}];
// x 必须正好包含 7 个 KV 对
map x = 1 [(validate.rules).map = {min_pairs: 7, max_pairs: 7}];
- **no_sparse**: 对于具有 message 值的 map 字段,将此规则设置为 true
将不允许键对应未设置的值。
// x 中的所有值都必须设置
map x = 1 [(validate.rules).map.no_sparse = true];
- **keys**: 此规则指定应用于字段中键的约束。
// x 的键必须全为负数
x = [(validate.rules).map.keys.sint32.lt = 0];
- **values**: 此规则指定应用于字段中
每个值的约束。除非在此约束上指定了 `skip`,否则 Repeated message 字段也会应用其验证规则。
// x 必须包含至少 3 个字符的字符串
map x = 1 [(validate.rules).map.values.string.min_len = 3];
// x 必须包含 Persons,但不验证它们
map x = 1 [(validate.rules).map.values.message.skip = true];
- **ignore_empty**: 此规则指定如果字段为空或设置为
默认值,则忽略任何验证规则。这通常在
需要在更新请求中取消设置字段,或者
对于切换到 WKTs 不可行的可选字段跳过验证时非常有用。
map x = 1 [(validate.rules).map = {ignore_empty: true, values: {string: {min_len: 3}}}];
### Well-Known Types (WKTs)
一组 [WKTs][wkts] 与 protoc 打包在一起,包含在许多领域有用的
常见 message 模式。
#### Scalar Value Wrappers
在 `proto3` 语法中,无法区分未设置和
标量字段的零值。值 WKTs 通过
将它们包装在 message 中来允许这种区分。PGV 允许使用与
包装器封装的标量相同的标量规则。
```
// if it is set, x must be greater than 3
google.protobuf.Int32Value x = 1 [(validate.rules).int32.gt = 3];
```
Message 规则也可以与标量 Well-Known Types (WKTs) 一起使用:
```
// Ensures that if a value is not set for age, it would not pass the validation despite its zero value being 0.
message X {google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, (validate.rules).message.required = true];}
```
#### Anys
- **required**: 此规则指定必须设置该字段
// x 不能被取消设置
google.protobuf.Any x = 1 [(validate.rules).any.required = true];
- **in/not_in**: 这两条规则允许为该字段中的
`type_url` 值指定允许/拒绝列表。如果可能,请考虑使用 `oneof` union 代替
`in`。
// x 不能是 Duration 或 Timestamp WKT
google.protobuf.Any x = 1 [(validate.rules).any = {not_in: [
"type.googleapis.com/google.protobuf.Duration",
"type.googleapis.com/google.protobuf.Timestamp"
]}];
#### Durations
- **required**: 此规则指定必须设置该字段
// x 不能被取消设置
google.protobuf.Duration x = 1 [(validate.rules).duration.required = true];
- **const**: 该字段必须_完全_等于指定的值。
// x 必须精确等于 1.5s
google.protobuf.Duration x = 1 [(validate.rules).duration.const = {
seconds: 1,
nanos: 500000000
}];
- **lt/lte/gt/gte**: 这些不等式(分别对应 `<`、`<=`、`>`、`>=`)
允许推导字段必须驻留的范围。
// x 必须小于 10s
google.protobuf.Duration x = 1 [(validate.rules).duration.lt.seconds = 10];
// x 必须大于或等于 20ns
google.protobuf.Duration x = 1 [(validate.rules).duration.gte.nanos = 20];
// x 必须在范围 [0s, 1s) 内
google.protobuf.Duration x = 1 [(validate.rules).duration = {
gte: {},
lt: {seconds: 1}
}];
反转 `lt(e)` 和 `gt(e)` 的值是有效的,这会创建一个互斥
范围。
// x 必须在范围 [0s, 1s) 之外
google.protobuf.Duration x = 1 [(validate.rules).duration = {
lt: {},
gte: {seconds: 1}
}];
- **in/not_in**: 这两条规则允许为字段的值指定允许/拒绝列表。
// x 必须是 0s 或 1s
google.protobuf.Duration x = 1 [(validate.rules).duration = {in: [
{},
{seconds: 1}
]}]
// x 不能是 20s 也不能是 500ns
google.protobuf.Duration x = 1 [(validate.rules).duration = {not_in: [
{seconds: 20},
{nanos: 500}
]}];
#### Timestamps
- **required**: 此规则指定必须设置该字段
// x 不能被取消设置
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.required = true];
- **const**: 该字段必须_完全_等于指定的值。
// x 必须精确等于 2009/11/10T23:00:00.500Z
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.const = {
seconds: 63393490800,
nanos: 500000000
}];
- **lt/lte/gt/gte**: 这些不等式(分别对应 `<`、`<=`、`>`、`>=`)
允许推导字段必须驻留的范围。
// x 必须小于 Unix Epoch
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.lt.seconds = 0];
// x 必须大于或等于 2009/11/10T23:00:00Z
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.gte.seconds = 63393490800];
// x 必须在范围 [epoch, 2009/11/10T23:00:00Z) 内
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp = {
gte: {},
lt: {seconds: 63393490800}
}];
反转 `lt(e)` 和 `gt(e)` 的值是有效的,这会创建一个互斥
范围。
// x 必须在范围 [epoch, 2009/11/10T23:00:00Z) 之外
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp = {
lt: {},
gte: {seconds: 63393490800}
}];
- **lt_now/gt_now**: 这些不等式允许相对于当前
时间的范围。这些规则不能与上述绝对规则一起使用。
// x 必须小于当前时间戳
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.lt_now = true];
- **within**: 此规则指定字段的值应在
当前时间的持续时间内。此规则可与 `lt_now` 和 `gt_now` 结合使用以控制这些范围。
// x 必须在当前时间的 ±1s 范围内
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.within.seconds = 1
// x 必须在范围 (now, now+1h) 内
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp = {
gt_now: true,
within: {seconds: 3600}
}];
### Message-Global
- **disabled**: 可以将 message 上字段的所有验证规则置为
无效,包括任何自身支持验证的 message 字段。
message Person {
option (validate.disabled) = true;
// x 将不会被要求大于 123
uint64 x = 1 [(validate.rules).uint64.gt = 123];
// y 的字段将不会被验证
Person y = 2;
}
- **ignored**: 不要为此 message 生成 validate 方法或任何相关的验证代码。
message Person {
option (validate.ignored) = true;
// x 将不会被要求大于 123
uint64 x = 1 [(validate.rules).uint64.gt = 123];
// y 的字段将不会被验证
Person y = 2;
}
### OneOfs
- **required**: 要求必须设置 `oneof` 中的其中一个字段。默认
情况下,可以设置 union 字段中的零个或一个。启用此规则
将不允许全部取消设置。
oneof id {
// x、y 或 z 必须设置一个。
option (validate.required) = true;
string x = 1;
int32 y = 2;
Person z = 3;
}
## 开发
PGV 使用 Go 编写,基于 [protoc-gen-star][pg*] 框架,并编译
为独立的二进制文件。
### 依赖项
PGV 的所有依赖项目前都已签入项目中。要测试
PGV,必须安装 `protoc`,可以从[源码][protoc-source]、提供的
[releases][protoc-releases] 或包管理器安装。还应安装目标
语言的官方 protoc 插件。
### Make Targets
- **`make build`**: 生成约束 proto 并将 PGV 编译
到 `$GOPATH/bin`
- **`make lint`**: 对 PGV 代码库运行静态分析规则,
包括 `golint`、`go vet` 和 `gofmt -s`
- **`make testcases`**: 生成 proto 文件到
[`/tests/harness/cases`](/tests/harness/cases)。这些文件由测试
harness 用于验证为每种语言生成的验证规则。
- **`make harness`**: 针对每种语言的测试
harness 执行测试用例。
### 在 Bazel 下运行所有测试
确保您的 `PATH` 设置为包含 `protoc-gen-go` 和 `protoc`,然后:
```
bazel test //tests/...
```
### Docker
PGV 附带一个 [Dockerfile](/Dockerfile),用于提供一致的开发工具
和 CI。主要入口点是 `make`,默认目标是 `build`。
```
# 构建 image
docker build -t bufbuild/protoc-gen-validate .
# 执行默认的 make target: build
docker run --rm \
bufbuild/protoc-gen-validate
# 执行 'ci' make target
docker run --rm \
bufbuild/protoc-gen-validate ci
# 执行 'build' & 'testcases' make targets
docker run --rm \
bufbuild/protoc-gen-validate build testcases
# 覆盖 entrypoint 并直接与 container 交互
# 这在想要运行 bazel commands 而没有
# 在本地安装 bazel 时非常有用。
docker run --rm \
-it --entrypoint=/bin/bash \
bufbuild/protoc-gen-validate
```
标签:EVTX分析, JS文件枚举, Protocol Buffers, protoc插件, Python工具, SOC Prime, 代码生成, 开发工具, 数据校验, 日志审计, 渗透测试工具, 逆向工具