quarks-tech/protoc-gen-validate

PHP 的 proto 验证库

0.7.0 2022-02-07 14:46 UTC

This package is not auto-updated.

Last update: 2022-05-18 14:51:49 UTC


README

该项目目前处于 alpha 阶段。API 应被视为不稳定且可能会更改

PGV 是一个 protoc 插件,用于生成多语言消息验证器。虽然协议缓冲区有效地保证了结构化数据类型,但它们无法强制执行值语义规则。此插件为 protoc 生成的代码添加了对这些约束的支持。

开发者导入 PGV 扩展,并在他们的 proto 文件中用约束规则注释消息和字段

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 }];
  }
}

使用 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@lyft.com"

err = p.Validate() // err: Name must match pattern '^[^\d\s]+( [^\d\s]+)*$'
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)
  • protoc 编译器在 $PATH
  • protoc-gen-validate$PATH
  • 针对目标语言(s)的官方语言特定插件
  • 目前仅支持 proto3 语法。 计划支持 proto2 语法。

安装

目前只能从源代码安装 PGV

# fetches this repo into $GOPATH
go get -d github.com/envoyproxy/protoc-gen-validate

# installs PGV into $GOPATH/bin
make build

参数

  • lang: 指定要生成的目标语言。目前,唯一支持的选项是
    • go
    • cc for c++(部分实现)
    • java
  • 注意:Python 通过运行时代码生成工作。没有编译时生成。有关详细信息,请参阅 Python 部分。

示例

Go

Go 生成应发生在与官方插件相同的输出路径中。对于 proto 文件 example.proto,相应的验证代码生成到 ../generated/example.pb.validate.go

protoc \
  -I . \
  -I ${GOPATH}/src \
  -I ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate \
  --go_out=":../generated" \
  --validate_out="lang=go:../generated" \
  example.proto

所有生成的消息都包含以下方法

  • Validate() error 返回验证期间遇到的第一个错误。
  • ValidateAll() error 返回验证期间遇到的全部错误。

PGV 对现有生成的代码不需要任何额外的运行时依赖。

注意:默认情况下,example.pb.validate.go 是嵌套在与您的 option go_package 名称匹配的目录结构中的。您可以使用 protoc 参数 paths=source_relative:. 来更改此结构。然后,--validate_out 将输出到预期的文件。有关更多信息,请参阅 Google 的 protobuf 文档或 packages and input pathsparameters

还支持 module=example.com/foo 标志 此处 描述。

Java

Java 生成与现有的 protobuf 工具链集成到 java 项目中。对于 Maven 项目,请在 pom.xml 或 build.gradle 中添加以下内容。

<dependencies>
    <dependency>
        <groupId> io.envoyproxy.protoc-gen-validate</groupId>
        <artifactId>pgv-java-stub</artifactId>
        <version>${pgv.version}</version>
    </dependency>
</dependencies>

<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.4.1.Final</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.6.1</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
            </configuration>
            <executions>
                <execution>
                    <id>protoc-java-pgv</id>
                    <goals>
                        <goal>compile-custom</goal>
                    </goals>
                    <configuration>
                        <pluginParameter>lang=java</pluginParameter>
                        <pluginId>java-pgv</pluginId>
                        <pluginArtifact>io.envoyproxy.protoc-gen-validate:protoc-gen-validate:${pgv.version}:exe:${os.detected.classifier}</pluginArtifact>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
plugins {
    ...
    id "com.google.protobuf" version "${protobuf.version}"
    ...
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:${protoc.version}"
    }

    plugins {
        javapgv {
            artifact = "io.envoyproxy.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执行。通过存储每个描述符生成的函数,LRU缓存提高了性能。

Python包可在PyPI上找到。

要运行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()函数查看已生成的代码。

约束规则

提供的约束主要模仿JSON Schema中的约束。PGV规则可以混合用于同一字段;插件确保在代码生成前应用于字段的规则不能相互矛盾。

查看约束规则比较矩阵以了解特定语言约束功能。

数值

所有数值类型(floatdoubleint32int64uint32uint64sint32sint64fixed32fixed64sfixed32sfixed64)共享相同的规则。

  • const:字段必须严格等于指定的值。

    // x must equal 1.23 exactly
    float x = 1 [(validate.rules).float.const = 1.23];
  • lt/lte/gt/gte:这些不等式(分别为<<=>>=)允许推导出字段必须存在的范围。

    // x must be less than 10
    int32 x = 1 [(validate.rules).int32.lt = 10];
    
    // x must be greater than or equal to 20
    uint64 x = 1 [(validate.rules).uint64.gte = 20];
    
    // x must be in the range [30, 40)
    fixed32 x = 1 [(validate.rules).fixed32 = {gte:30, lt: 40}];

    反转lt(e)gt(e)的值是有效的,并创建一个排他性范围。

    // x must be outside the range [30, 40)
    double x = 1 [(validate.rules).double = {lt:30, gte:40}];
  • in/not_in:这两个规则允许指定字段的值允许/拒绝列表。

    // x must be either 1, 2, or 3
    uint32 x = 1 [(validate.rules).uint32 = {in: [1,2,3]}];
    
    // x cannot be 0 nor 0.99
    float x = 1 [(validate.rules).float = {not_in: [0, 0.99]}];
  • ignore_empty:此规则指定如果字段为空或设置为默认值,则忽略任何验证规则。这些通常在可以在更新请求中取消设置字段或跳过验证可选字段(在这种情况下无法切换到WKTs)时很有用。

    unint32 x = 1 [(validate.rules).uint32 = {ignore_empty: true, gte: 200}];

布尔值

  • const:字段必须严格等于指定的值。

    // x must be set to true
    bool x = 1 [(validate.rules).bool.const = true];
    
    // x cannot be set to true
    bool x = 1 [(validate.rules).bool.const = false];

字符串

  • const:字段必须严格等于指定的值。

    // x must be set to "foo"
    string x = 1 [(validate.rules).string.const = "foo"];
  • len/min_len/max_len:这些规则限制了字段中的字符数(Unicode代码点数)。请注意,字符数可能与字符串中的字节数不同。字符串按原样考虑,不进行规范化。

    // x must be exactly 5 characters long
    string x = 1 [(validate.rules).string.len = 5];
    
    // x must be at least 3 characters long
    string x = 1 [(validate.rules).string.min_len = 3];
    
    // x must be between 5 and 10 characters, inclusive
    string x = 1 [(validate.rules).string = {min_len: 5, max_len: 10}];
  • min_bytes/max_bytes:这些规则限制了字段中的字节数。

    // x must be at most 15 bytes long
    string x = 1 [(validate.rules).string.max_bytes = 15];
    
    // x must be between 128 and 1024 bytes long
    string x = 1 [(validate.rules).string = {min_bytes: 128, max_bytes: 1024}];
  • pattern:字段必须匹配指定的RE2兼容正则表达式。包含的表达式应省略任何分隔符(例如,/\d+/应仅是\d+)。

    // x must be a non-empty, case-insensitive hexadecimal string
    string x = 1 [(validate.rules).string.pattern = "(?i)^[0-9a-f]+$"];
  • prefix/suffix/contains/not_contains:字段必须包含指定的子串,或在可选的显式位置中不包含指定的子串。

    // x must begin with "foo"
    string x = 1 [(validate.rules).string.prefix = "foo"];
    
    // x must end with "bar"
    string x = 1 [(validate.rules).string.suffix = "bar"];
    
    // x must contain "baz" anywhere inside it
    string x = 1 [(validate.rules).string.contains = "baz"];
    
    // x cannot contain "baz" anywhere inside it
    string x = 1 [(validate.rules).string.not_contains = "baz"];
    
    // x must begin with "fizz" and end with "buzz"
    string x = 1 [(validate.rules).string = {prefix: "fizz", suffix: "buzz"}];
    
    // x must end with ".proto" and be less than 64 characters
    string x = 1 [(validate.rules).string = {suffix: ".proto", max_len:64}];
  • in/not_in:这两个规则允许指定字段的值允许/拒绝列表。

    // x must be either "foo", "bar", or "baz"
    string x = 1 [(validate.rules).string = {in: ["foo", "bar", "baz"]}];
    
    // x cannot be "fizz" nor "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 must be a valid email address (via RFC 1034)
    string x = 1 [(validate.rules).string.email = true];
    
    // x must be a valid address (IP or Hostname).
    string x = 1 [(validate.rules).string.address = true];
    
    // x must be a valid hostname (via RFC 1034)
    string x = 1 [(validate.rules).string.hostname = true];
    
    // x must be a valid IP address (either v4 or v6)
    string x = 1 [(validate.rules).string.ip = true];
    
    // x must be a valid IPv4 address
    // eg: "192.168.0.1"
    string x = 1 [(validate.rules).string.ipv4 = true];
    
    // x must be a valid IPv6 address
    // eg: "fe80::3"
    string x = 1 [(validate.rules).string.ipv6 = true];
    
    // x must be a valid absolute URI (via RFC 3986)
    string x = 1 [(validate.rules).string.uri = true];
    
    // x must be a valid URI reference (either absolute or relative)
    string x = 1 [(validate.rules).string.uri_ref = true];
    
    // x must be a valid UUID (via RFC 4122)
    string x = 1 [(validate.rules).string.uuid = true];
    
    // x must conform to a well known regex for HTTP header names (via RFC 7230)
    string x = 1 [(validate.rules).string.well_known_regex = HTTP_HEADER_NAME]
    
    // x must conform to a well known regex for HTTP header values (via RFC 7230) 
    string x = 1 [(validate.rules).string.well_known_regex = HTTP_HEADER_VALUE];
    
    // x must conform to a well known regex for headers, disallowing \r\n\0 characters.
    string x = 1 [(validate.rules).string {well_known_regex: HTTP_HEADER_VALUE, strict: false}];

字节

应使用字符串表示字面值,在必要时使用转义。

  • const:字段必须严格等于指定的值。

    // x must be set to "foo" ("\x66\x6f\x6f")
    bytes x = 1 [(validate.rules).bytes.const = "foo"];
    
    // x must be set to "\xf0\x90\x28\xbc"
    bytes x = 1 [(validate.rules).bytes.const = "\xf0\x90\x28\xbc"];
  • len/min_len/max_len:这些规则限制了字段中的字节数。

    // x must be exactly 3 bytes
    bytes x = 1 [(validate.rules).bytes.len = 3];
    
    // x must be at least 3 bytes long
    bytes x = 1 [(validate.rules).bytes.min_len = 3];
    
    // x must be between 5 and 10 bytes, inclusive
    bytes x = 1 [(validate.rules).bytes = {min_len: 5, max_len: 10}];
  • pattern:字段必须匹配指定的RE2兼容正则表达式。包含的表达式应省略任何分隔符(例如,/\d+/应仅是\d+)。

    // x must be a non-empty, ASCII byte sequence
    bytes x = 1 [(validate.rules).bytes.pattern = "^[\x00-\x7F]+$"];
  • prefix/suffix/contains:字段必须包含指定的字节序列,或在可选的显式位置中包含。

    // x must begin with "\x99"
    bytes x = 1 [(validate.rules).bytes.prefix = "\x99"];
    
    // x must end with "buz\x7a"
    bytes x = 1 [(validate.rules).bytes.suffix = "buz\x7a"];
    
    // x must contain "baz" anywhere inside it
    bytes x = 1 [(validate.rules).bytes.contains = "baz"];
  • in/not_in:这两个规则允许指定字段的值允许/拒绝列表。

    // x must be either "foo", "bar", or "baz"
    bytes x = 1 [(validate.rules).bytes = {in: ["foo", "bar", "baz"]}];
    
    // x cannot be "fizz" nor "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"]}];
  • 常用格式:这些规则为常见模式提供了高级约束。这些约束通常比等效的正则表达式模式更为宽容和高效,同时提供更详细的失败描述。

    // x must be a valid IP address (either v4 or v6) in byte format
    bytes x = 1 [(validate.rules).bytes.ip = true];
    
    // x must be a valid IPv4 address in byte format
    // eg: "\xC0\xA8\x00\x01"
    bytes x = 1 [(validate.rules).bytes.ipv4 = true];
    
    // x must be a valid IPv6 address in byte format
    // eg: "\x20\x01\x0D\xB8\x85\xA3\x00\x00\x00\x00\x8A\x2E\x03\x70\x73\x34"
    bytes x = 1 [(validate.rules).bytes.ipv6 = true];

枚举

所有字面值都应该使用枚举描述符中定义的数值(int32)。

以下示例使用了此 State 枚举

enum State {
  INACTIVE = 0;
  PENDING  = 1;
  ACTIVE   = 2;
}
  • const:字段必须严格等于指定的值。

    // x must be set to ACTIVE (2)
    State x = 1 [(validate.rules).enum.const = 2];
  • 仅定义:字段必须是枚举描述符中指定的值之一。

    // x can only be INACTIVE, PENDING, or ACTIVE
    State x = 1 [(validate.rules).enum.defined_only = true];
  • in/not_in:这两个规则允许指定字段的值允许/拒绝列表。

    // x must be either INACTIVE (0) or ACTIVE (2)
    State x = 1 [(validate.rules).enum = {in: [0,2]}];
    
    // x cannot be PENDING (1)
    State x = 1 [(validate.rules).enum = {not_in: [1]}];

消息

如果一个字段包含消息,并且该消息是由 PGV 生成的,则将递归地进行验证。未使用 PGV 生成的消息将被跳过。

// if Person was generated with PGV and x is set,
// x's fields will be validated.
Person x = 1;
  • 跳过:此规则指定不应评估此字段的验证规则。

    // The fields on Person x will not be validated.
    Person x = 1 [(validate.rules).message.skip = true];
  • 必需:此规则指定字段不能被清除。

    // x cannot be unset
    Person x = 1 [(validate.rules).message.required = true];
    
    // x cannot be unset, but the validations on x will not be performed
    Person x = 1 [(validate.rules).message = {required: true, skip: true}];

重复

  • min_items/max_items:这些规则控制字段中包含的元素数量

    // x must contain at least 3 elements
    repeated int32 x = 1 [(validate.rules).repeated.min_items = 3];
    
    // x must contain between 5 and 10 Persons, inclusive
    repeated Person x = 1 [(validate.rules).repeated = {min_items: 5, max_items: 10}];
    
    // x must contain exactly 7 elements
    repeated double x = 1 [(validate.rules).repeated = {min_items: 7, max_items: 7}];
  • 唯一:此规则要求字段中的所有元素必须是唯一的。此规则不支持重复的消息。

    // x must contain unique int64 values
    repeated int64 x = 1 [(validate.rules).repeated.unique = true];
  • items:此规则指定应用于字段中每个元素的约束。重复消息字段也应用其验证规则,除非在此约束中指定了 skip

    // x must contain positive float values
    repeated float x = 1 [(validate.rules).repeated.items.float.gt = 0];
    
    // x must contain Persons but don't validate them
    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}}}];

映射

  • min_pairs/max_pairs:这些规则控制此字段中包含的键值对数量

    // x must contain at most 3 KV pairs
    map<string, uint64> x = 1 [(validate.rules).map.min_pairs = 3];
    
    // x must contain between 5 and 10 KV pairs
    map<string, string> x = 1 [(validate.rules)].map = {min_pairs: 5, max_pairs: 10}];
    
    // x must contain exactly 7 KV pairs
    map<string, Person> x = 1 [(validate.rules)].map = {min_pairs: 7, max_pairs: 7}];
  • no_sparse:对于具有消息值的映射字段,将此规则设置为 true 将不允许具有未设置值的键。

    // all values in x must be set
    map<uint64, Person> x = 1 [(validate.rules).map.no_sparse = true];
  • keys:此规则指定应用于字段中键的约束。

    // x's keys must all be negative
    <sint32, string> x = [(validate.rules).map.keys.sint32.lt = 0];
  • values:此规则指定应用于字段中每个值的约束。重复消息字段也应用其验证规则,除非在此约束中指定了 skip

    // x must contain strings of at least 3 characters
    map<string, string> x = 1 [(validate.rules).map.values.string.min_len = 3];
    
    // x must contain Persons but doesn't validate them
    map<string, Person> x = 1 [(validate.rules).map.values.message.skip = true];
  • ignore_empty:此规则指定如果字段为空或设置为默认值,则忽略任何验证规则。这些通常在可以在更新请求中取消设置字段或跳过验证可选字段(在这种情况下无法切换到WKTs)时很有用。

    map<string, string> x = 1 [(validate.rules).map = {ignore_empty: true, values: {string: {min_len: 3}}}];

已知类型(WKTs)

一组 WKTs 随 protoc 包装,并包含许多领域中有用的常见消息模式。

标量值包装器

proto3 语法中,无法区分未设置和标量字段的零值。值 WKTs 通过将它们包装在消息中允许这种区分。PGV 允许使用包装器封装的相同标量规则。

// if it is set, x must be greater than 3
google.protobuf.Int32Value x = 1 [(validate.rules).int32.gt = 3];

消息规则也可以与标量已知类型(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]; }

任意的

  • 必需:此规则指定字段必须被设置

    // x cannot be unset
    google.protobuf.Any x = 1 [(validate.rules).any.required = true];
  • in/not_in:这两个规则允许指定此字段中 type_url 值的允许/拒绝列表。如果可能,考虑使用 oneof 联合代替 in

    // x must not be the Duration or Timestamp WKT
    google.protobuf.Any x = 1 [(validate.rules).any = {not_in: [
        "type.googleapis.com/google.protobuf.Duration",
        "type.googleapis.com/google.protobuf.Timestamp"
      ]}];

持续时间

  • 必需:此规则指定字段必须被设置

    // x cannot be unset
    google.protobuf.Duration x = 1 [(validate.rules).duration.required = true];
  • const:字段必须严格等于指定的值。

    // x must equal 1.5s exactly
    google.protobuf.Duration x = 1 [(validate.rules).duration.const = {
        seconds: 1,
        nanos:   500000000
      }];
  • lt/lte/gt/gte:这些不等式(分别为<<=>>=)允许推导出字段必须存在的范围。

    // x must be less than 10s
    google.protobuf.Duration x = 1 [(validate.rules).duration.lt.seconds = 10];
    
    // x must be greater than or equal to 20ns
    google.protobuf.Duration x = 1 [(validate.rules).duration.gte.nanos = 20];
    
    // x must be in the range [0s, 1s)
    google.protobuf.Duration x = 1 [(validate.rules).duration = {
        gte: {},
        lt:  {seconds: 1}
      }];

    反转lt(e)gt(e)的值是有效的,并创建一个排他性范围。

    // x must be outside the range [0s, 1s)
    google.protobuf.Duration x = 1 [(validate.rules).duration = {
        lt:  {},
        gte: {seconds: 1}
      }];
  • in/not_in:这两个规则允许指定字段的值允许/拒绝列表。

    // x must be either 0s or 1s
    google.protobuf.Duration x = 1 [(validate.rules).duration = {in: [
        {},
        {seconds: 1}
      ]}];
    
    // x cannot be 20s nor 500ns
    google.protobuf.Duration x = 1 [(validate.rules).duration = {not_in: [
        {seconds: 20},
        {nanos: 500}
      ]}];

时间戳

  • 必需:此规则指定字段必须被设置

    // x cannot be unset
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.required = true];
  • const:字段必须严格等于指定的值。

    // x must equal 2009/11/10T23:00:00.500Z exactly
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.const = {
        seconds: 63393490800,
        nanos:   500000000
      }];
  • lt/lte/gt/gte:这些不等式(分别为<<=>>=)允许推导出字段必须存在的范围。

    // x must be less than the Unix Epoch
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.lt.seconds = 0];
    
    // x must be greater than or equal to 2009/11/10T23:00:00Z
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.gte.seconds = 63393490800];
    
    // x must be in the range [epoch, 2009/11/10T23:00:00Z)
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp = {
        gte: {},
        lt:  {seconds: 63393490800}
      }];

    反转lt(e)gt(e)的值是有效的,并创建一个排他性范围。

    // x must be outside the range [epoch, 2009/11/10T23:00:00Z)
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp = {
        lt:  {},
        gte: {seconds: 63393490800}
      }];
  • lt_now/gt_now:这些不等式允许相对于当前时间的范围。这些规则不能与上面的绝对规则一起使用。

    // x must be less than the current timestamp
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.lt_now = true];
  • within:此规则指定字段值应在当前时间范围内。此规则可与lt_nowgt_now一起使用,以控制这些范围。

    // x must be within ±1s of the current time
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.within.seconds = 1];
    
    // x must be within the range (now, now+1h)
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp = {
        gt_now: true,
        within: {seconds: 3600}
      }];

消息-全局

  • 禁用:可以取消消息中所有字段的验证规则,包括支持自身验证的消息字段。

    message Person {
      option (validate.disabled) = true;
    
      // x will not be required to be greater than 123
      uint64 x = 1 [(validate.rules).uint64.gt = 123];
    
      // y's fields will not be validated
      Person y = 2;
    }
  • 忽略:不为此消息生成验证方法或任何相关验证代码。

    message Person {
      option (validate.ignored) = true;
    
      // x will not be required to be greater than 123
      uint64 x = 1 [(validate.rules).uint64.gt = 123];
    
      // y's fields will not be validated
      Person y = 2;
    }

OneOfs

  • 必需:要求oneof中的一个字段必须设置。默认情况下,联合字段可以是空的或其中一个被设置。启用此规则将不允许所有字段都不设置。

    oneof id {
      // either x, y, or z must be set.
      option (validate.required) = true;
    
      string x = 1;
      int32  y = 2;
      Person z = 3;
    }

开发

PGV是在protoc-gen-star框架之上用Go编写的,并编译为独立的二进制文件。

依赖项

所有PGV依赖项目前都已提交到项目中。要测试PGV,必须安装protoc,可以从源代码、提供的发布版本或包管理器安装。还应该安装目标语言的官方protoc插件。

Make 目标

  • make build:生成约束proto并将其编译到$GOPATH/bin

  • make lint:对PGV代码库运行静态分析规则,包括golintgo vetgofmt -s

  • make testcases:在/tests/harness/cases中生成proto文件。这些文件由测试框架用于验证为每种语言生成的验证规则。

  • make harness:针对每种语言的测试框架执行测试用例。

在Bazel下运行所有测试

确保您的PATH已设置以包括protoc-gen-goprotoc,然后

bazel test //tests/...

Docker

PGV附带一个Dockerfile,用于一致的开发工具和CI。主要入口点是带有默认目标的make。此存储库应挂载到/go/src/github.com/envoyproxy/protoc-gen-validate以正确运行。

# build the image
docker build -t lyft/protoc-gen-validate .

# executes the default make target: quick
docker run --rm \
  -v $(PWD):/go/src/github.com/envoyproxy/protoc-gen-validate \
  lyft/protoc-gen-validate

# executes the 'build' & 'generate-testdata' make targets
docker run --rm \
  -v $(PWD):/go/src/github.com/envoyproxy/protoc-gen-validate \
  lyft/protoc-gen-validate \
  build generate-testdata