bufbuild/protoc-gen-validate

GitHub: bufbuild/protoc-gen-validate

一个 protoc 插件,通过 proto 文件中的注解为 Protocol Buffers 消息自动生成多语言的字段值语义验证代码。

Stars: 4122 | Forks: 605

# [![](https://static.pigsec.cn/wp-content/uploads/repos/cas/19/1909027305f974fe55cf8ba75768f9a0bbb274ec6a7b0f7d0161ffed6b89f578.svg)][buf] protoc-gen-validate (PGV) ![License](https://img.shields.io/github/license/bufbuild/protoc-gen-validate?color=blue) ![Release](https://img.shields.io/github/v/release/bufbuild/protoc-gen-validate?include_prereleases) ![Slack](https://img.shields.io/badge/slack-buf-%23e01563) 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, 代码生成, 开发工具, 数据校验, 日志审计, 渗透测试工具, 逆向工具