• Stars
    star
    3,638
  • Rank 11,639 (Top 0.3 %)
  • Language
    Go
  • License
    Apache License 2.0
  • Created over 6 years ago
  • Updated 22 days ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Protocol Buffer Validation - Being replaced by github.com/bufbuild/protovalidate

protoc-gen-validate (PGV)

License Release Slack

New: The next generation of PGV, called protovalidate, is now available with a beta release for Go! We're hard at work on C++, Java, TypeScript, and Python implementations as well. To learn more, check out our blog post. We value your input in refining our products, so don't hesitate to share your feedback on protovalidate.

PGV is a protoc plugin to generate polyglot message validators. While protocol buffers effectively guarantee the types of structured data, they cannot enforce semantic rules for values. This plugin adds support to protoc-generated code to validate such constraints.

Developers import the PGV extension and annotate the messages and fields in their proto files with constraint rules:

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

Executing protoc with PGV and the target language's default plugin will create Validate methods on the generated types:

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 = "[email protected]"

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

Usage

Dependencies

  • go toolchain (β‰₯ v1.7)
  • protoc compiler in $PATH
  • protoc-gen-validate in $PATH
  • official language-specific plugin for target language(s)
  • Only proto3 syntax is currently supported. proto2 syntax support is planned.

Installation

Download from GitHub Releases

Download assets from GitHub Releases and unarchive them and add plugins into $PATH.

Build from source

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

πŸ’‘ Yes, our go module path is github.com/envoyproxy/protoc-gen-validate not bufbuild this is intentional.

Changing the module path is effectively creating a new, independent module. We would prefer not to break our users. The Go team are working on better cmd/go support for modules that change paths, but progress is slow. Until then, we will continue to use the envoyproxy module path.

git clone github.com/bufbuild/protoc-gen-validate
# installs PGV into $GOPATH/bin
cd protoc-gen-validate && make build

Parameters

  • lang: specify the target language to generate. Currently, the only supported options are:
    • go
    • cc for c++ (partially implemented)
    • java
  • Note: Python works via runtime code generation. There's no compile-time generation. See the Python section for details.

Examples

Go

Go generation should occur into the same output path as the official plugin. For a proto file example.proto, the corresponding validation code is generated into ../generated/example.pb.validate.go:

protoc \
  -I . \
  -I path/to/validate/ \
  --go_out=":../generated" \
  --validate_out="lang=go:../generated" \
  example.proto

All messages generated include the following methods:

  • Validate() error which returns the first error encountered during validation.
  • ValidateAll() error which returns all errors encountered during validation.

PGV requires no additional runtime dependencies from the existing generated code.

Note: by default example.pb.validate.go is nested in a directory structure that matches your option go_package name. You can change this using the protoc parameter paths=source_relative:., as like --validate_out="lang=go,paths=source_relative:../generated". Then --validate_out will output the file where it is expected. See Google's protobuf documentation or packages and input paths or parameters for more information.

There's also support for the module=example.com/foo flag described here .

With newer Buf CLI versions (>v1.9.0), you can use the new plugin key instead of using the protoc command directly:

# 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 generation is integrated with the existing protobuf toolchain for java projects. For Maven projects, add the following to your pom.xml or build.gradle.

<dependencies>
    <dependency>
        <groupId>build.buf.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>
                        build.buf.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 = "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

The python implementation works via JIT code generation. In other words, the validate(msg) function is written on-demand and exec-ed. An LRU-cache improves performance by storing generated functions per descriptor.

The python package is available on PyPI.

To run validate(), do the following:

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)

You can view what code has been generated by using the print_validate() function.

Constraint Rules

The provided constraints are modeled largerly after those in JSON Schema. PGV rules can be mixed for the same field; the plugin ensures the rules applied to a field cannot contradict before code generation.

Check the constraint rule comparison matrix for language-specific constraint capabilities.

Numerics

All numeric types (float, double, int32, int64, uint32, uint64 , sint32, sint64, fixed32, fixed64, sfixed32, sfixed64) share the same rules.

  • const: the field must be exactly the specified value.

    // x must equal 1.23 exactly
    float x = 1 [(validate.rules).float.const = 1.23];
  • lt/lte/gt/gte: these inequalities (<, <=, >, >=, respectively) allow for deriving ranges in which the field must reside.

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

    Inverting the values of lt(e) and gt(e) is valid and creates an exclusive range.

    // x must be outside the range [30, 40)
    double x = 1 [(validate.rules).double = {lt:30, gte:40}];
  • in/not_in: these two rules permit specifying allow/denylists for the values of a field.

    // 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: this rule specifies that if field is empty or set to the default value, to ignore any validation rules. These are typically useful where being able to unset a field in an update request, or to skip validation for optional fields where switching to WKTs is not feasible.

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

Bools

  • const: the field must be exactly the specified value.

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

Strings

  • const: the field must be exactly the specified value.

    // x must be set to "foo"
    string x = 1 [(validate.rules).string.const = "foo"];
  • len/min_len/max_len: these rules constrain the number of characters ( Unicode code points) in the field. Note that the number of characters may differ from the number of bytes in the string. The string is considered as-is, and does not normalize.

    // 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: these rules constrain the number of bytes in the field.

    // 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: the field must match the specified RE2-compliant regular expression. The included expression should elide any delimiters (ie, /\d+/ should just be \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: the field must contain the specified substring in an optionally explicit location, or not contain the specified substring.

    // 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: these two rules permit specifying allow/denylists for the values of a field.

    // 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: this rule specifies that if field is empty or set to the default value, to ignore any validation rules. These are typically useful where being able to unset a field in an update request, or to skip validation for optional fields where switching to WKTs is not feasible.

    string CountryCode = 1 [(validate.rules).string = {ignore_empty: true, len: 2}];
  • well-known formats: these rules provide advanced constraints for common string patterns. These constraints will typically be more permissive and performant than equivalent regular expression patterns, while providing more explanatory failure descriptions.

    // x must be a valid email address (via RFC 5322)
    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}];

Bytes

Literal values should be expressed with strings, using escaping where necessary.

  • const: the field must be exactly the specified value.

    // 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: these rules constrain the number of bytes in the field.

    // 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: the field must match the specified RE2-compliant regular expression. The included expression should elide any delimiters (ie, /\d+/ should just be \d+).

    // x must be a non-empty, ASCII byte sequence
    bytes x = 1 [(validate.rules).bytes.pattern = "^[\x00-\x7F]+$"];
  • prefix/suffix/contains: the field must contain the specified byte sequence in an optionally explicit location.

    // 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: these two rules permit specifying allow/denylists for the values of a field.

    // 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: this rule specifies that if field is empty or set to the default value, to ignore any validation rules. These are typically useful where being able to unset a field in an update request, or to skip validation for optional fields where switching to WKTs is not feasible.

    bytes x = 1 [(validate.rules).bytes = {ignore_empty: true, in: ["foo", "bar", "baz"]}];
  • well-known formats: these rules provide advanced constraints for common patterns. These constraints will typically be more permissive and performant than equivalent regular expression patterns, while providing more explanatory failure descriptions.

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

Enums

All literal values should use the numeric (int32) value as defined in the enum descriptor.

The following examples use this State enum

enum State {
  INACTIVE = 0;
  PENDING = 1;
  ACTIVE = 2;
}
  • const: the field must be exactly the specified value.

    // x must be set to ACTIVE (2)
    State x = 1 [(validate.rules).enum.const = 2];
  • defined_only: the field must be one of the specified values in the enum descriptor.

    // x can only be INACTIVE, PENDING, or ACTIVE
    State x = 1 [(validate.rules).enum.defined_only = true];
  • in/not_in: these two rules permit specifying allow/denylists for the values of a field.

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

Messages

If a field contains a message and the message has been generated with PGV, validation will be performed recursively. Message's not generated with PGV are skipped.

// if Person was generated with PGV and x is set,
// x's fields will be validated.
    Person x = 1;
  • skip: this rule specifies that the validation rules of this field should not be evaluated.

    // The fields on Person x will not be validated.
    Person x = 1 [(validate.rules).message.skip = true];
  • required: this rule specifies that the field cannot be unset.

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

Repeated

  • min_items/max_items: these rules control how many elements are contained in the field

    // 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}];
  • unique: this rule requires that all elements in the field must be unique. This rule does not support repeated messages.

    // x must contain unique int64 values
    repeated int64 x = 1 [(validate.rules).repeated.unique = true];
  • items: this rule specifies constraints that should be applied to each element in the field. Repeated message fields also have their validation rules applied unless skip is specified on this constraint.

    // 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: this rule specifies that if field is empty or set to the default value, to ignore any validation rules. These are typically useful where being able to unset a field in an update request, or to skip validation for optional fields where switching to WKTs is not feasible.

    repeated int64 x = 1 [(validate.rules).repeated = {ignore_empty: true, items: {int64: {gt: 200}}}];

Maps

  • min_pairs/max_pairs: these rules control how many KV pairs are contained in this field

    // x must contain at least 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: for map fields with message values, setting this rule to true disallows keys with unset values.

    // all values in x must be set
    map<uint64, Person> x = 1 [(validate.rules).map.no_sparse = true];
  • keys: this rule specifies constraints that are applied to the keys in the field.

    // x's keys must all be negative
    <sint32, string> x = [(validate.rules).map.keys.sint32.lt = 0];
  • values: this rule specifies constraints that are be applied to each value in the field. Repeated message fields also have their validation rules applied unless skip is specified on this constraint.

    // 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: this rule specifies that if field is empty or set to the default value, to ignore any validation rules. These are typically useful where being able to unset a field in an update request, or to skip validation for optional fields where switching to WKTs is not feasible.

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

Well-Known Types (WKTs)

A set of WKTs are packaged with protoc and common message patterns useful in many domains.

Scalar Value Wrappers

In the proto3 syntax, there is no way of distinguishing between unset and the zero value of a scalar field. The value WKTs permit this differentiation by wrapping them in a message. PGV permits using the same scalar rules that the wrapper encapsulates.

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

Message Rules can also be used with scalar 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: this rule specifies that the field must be set

    // x cannot be unset
    google.protobuf.Any x = 1 [(validate.rules).any.required = true];
  • in/not_in: these two rules permit specifying allow/denylists for the type_url value in this field. Consider using a oneof union instead of in if possible.

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

Durations

  • required: this rule specifies that the field must be set

    // x cannot be unset
    google.protobuf.Duration x = 1 [(validate.rules).duration.required = true];
  • const: the field must be exactly the specified value.

    // x must equal 1.5s exactly
    google.protobuf.Duration x = 1 [(validate.rules).duration.const = {
        seconds: 1,
        nanos:   500000000
      }];
  • lt/lte/gt/gte: these inequalities (<, <=, >, >=, respectively) allow for deriving ranges in which the field must reside.

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

    Inverting the values of lt(e) and gt(e) is valid and creates an exclusive range.

    // x must be outside the range [0s, 1s)
    google.protobuf.Duration x = 1 [(validate.rules).duration = {
        lt:  {},
        gte: {seconds: 1}
      }];
  • in/not_in: these two rules permit specifying allow/denylists for the values of a field.

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

Timestamps

  • required: this rule specifies that the field must be set

    // x cannot be unset
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.required = true];
  • const: the field must be exactly the specified value.

    // 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: these inequalities (<, <=, >, >=, respectively) allow for deriving ranges in which the field must reside.

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

    Inverting the values of lt(e) and gt(e) is valid and creates an exclusive range.

    // 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: these inequalities allow for ranges relative to the current time. These rules cannot be used with the absolute rules above.

    // x must be less than the current timestamp
    google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.lt_now = true];
  • within: this rule specifies that the field's value should be within a duration of the current time. This rule can be used in conjunction with lt_now and gt_now to control those ranges.

    // 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-Global

  • disabled: All validation rules for the fields on a message can be nullified, including any message fields that support validation themselves.

    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;
    }
  • ignored: Don't generate a validate method or any related validation code for this message.

    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

  • required: require that one of the fields in a oneof must be set. By default, none or one of the unioned fields can be set. Enabling this rules disallows having all of them unset.

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

Development

PGV is written in Go on top of the protoc-gen-star framework and compiles to a standalone binary.

Dependencies

All PGV dependencies are currently checked into the project. To test PGV, protoc must be installed, either from source, the provided releases, or a package manager. The official protoc plugin for the target language(s) should be installed as well.

Make Targets

  • make build: generates the constraints proto and compiles PGV into $GOPATH/bin

  • make lint: runs static-analysis rules against the PGV codebase, including golint, go vet, and gofmt -s

  • make testcases: generates the proto files in /tests/harness/cases. These are used by the test harness to verify the validation rules generated for each language.

  • make harness: executes the test-cases against each language's test harness.

Run all tests under Bazel

Ensure that your PATH is setup to include protoc-gen-go and protoc, then:

bazel test //tests/...

Docker

PGV comes with a Dockerfile for consistent development tooling and CI. The main entrypoint is make with build as the default target.

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

# executes the default make target: build
docker run --rm \
  bufbuild/protoc-gen-validate

# executes the 'ci' make target
docker run --rm \
  bufbuild/protoc-gen-validate ci

# executes the 'build' & 'testcases' make targets
docker run --rm \
  bufbuild/protoc-gen-validate build testcases

# override the entrypoint and interact with the container directly
# this can be useful when wanting to run bazel commands without
# bazel installed locally.
docker run --rm \
 -it --entrypoint=/bin/bash \
 bufbuild/protoc-gen-validate

More Repositories

1

buf

The best way of working with Protocol Buffers.
Go
8,208
star
2

connect-es

Connect, gRPC, and gRPC-Web support for Protobuf and TypeScript.
TypeScript
980
star
3

protobuf-es

Protocol Buffers for ECMAScript. The only JavaScript Protobuf library that is fully-compliant with Protobuf conformance tests.
TypeScript
927
star
4

protovalidate

Protocol Buffer Validation - Go, Java, Python, and C++ Beta Releases!
Go
604
star
5

protocompile

A parsing/linking engine for protobuf; the guts for a pure Go replacement of protoc.
Go
199
star
6

protovalidate-go

Protocol Buffer Validation for Go
Go
198
star
7

knit

GraphQL-like capabilities to services using Protocol Buffers, gRPC, and Connect
Go
136
star
8

buf-language-server

Prototype for a Protobuf language server compatible with Buf.
Go
116
star
9

connect-query

TypeScript-first expansion pack for TanStack Query that gives you Protobuf superpowers
TypeScript
109
star
10

makego

Makefile setup for our Golang projects.
Makefile
96
star
11

connect-opentelemetry-go

OpenTelemetry tracing and metrics for Connect
Go
81
star
12

connect-es-integration

Examples for using Connect with various TypeScript web frameworks and tooling
TypeScript
71
star
13

buf-examples

Example repository that uses Buf.
C#
69
star
14

connect-demo

An example service built with Connect.
Go
66
star
15

vscode-buf

Visual Studio Code integration for Buf.
TypeScript
56
star
16

connect-swift

Idiomatic gRPC & Connect RPCs for Swift.
Swift
48
star
17

knit-go

Knit standalone gateway and Go embeddable gateway
Go
45
star
18

prototransform

Client library for Buf Reflection API, for transforming Protobuf data.
Go
42
star
19

buf-gradle-plugin

Gradle plugin for the Buf CLI
Kotlin
42
star
20

connect-kotlin

Idiomatic gRPC & Connect RPCs for Kotlin.
Kotlin
42
star
21

buf-tour

Go
41
star
22

plugins

Remote Protobuf plugins available on the BSR
Dockerfile
40
star
23

connect-grpcreflect-go

gRPC-compatible server reflection for any net/http server.
Go
39
star
24

rules_buf

Bazel rules for Buf.
Starlark
37
star
25

vim-buf

Vim integration for Buf.
Vim Script
36
star
26

buf-setup-action

TypeScript
36
star
27

httplb

Client-side load balancing for net/http
Go
36
star
28

connect-grpchealth-go

gRPC-compatible health checks for any net/http server.
Go
32
star
29

buf-lint-action

TypeScript
28
star
30

protovalidate-python

Protocol Buffer Validation for Python.
Python
27
star
31

protovalidate-java

Protocol Buffer Validation for Java.
Java
26
star
32

knit-ts

TypeScript client for Knit
TypeScript
25
star
33

protoyaml-go

Marshal and unmarshal Protobuf as YAML with rich error messages.
Go
25
star
34

connect-crosstest

Connect's gRPC and gRPC-Web interoperability test suite.
C++
23
star
35

protobuf.com

Buf's Guide to Protobuf. Home of the language spec and grammar for the Protobuf IDL.
CSS
21
star
36

buf-breaking-action

TypeScript
20
star
37

protobuf-conformance

A repository running the Protobuf conformance tests against various libraries
JavaScript
20
star
38

modules

Collection of third-party modules managed and synced by Buf.
Go
19
star
39

buf-push-action

Shell
15
star
40

intellij-buf

IntelliJ plugin for Buf
Kotlin
15
star
41

protoplugin

The missing library to write protoc plugins.
Go
13
star
42

registry-proto

BSR's new public API. Currently in development.
Makefile
12
star
43

homebrew-buf

Homebrew tap for Buf.
Shell
12
star
44

protovalidate-cc

Protocol Buffer Validation for C++.
C++
11
star
45

docs.buf.build

The source for https://docs.buf.build.
TypeScript
11
star
46

protoschema-plugins

Protobuf plugins that generate various schemas from protobuf files - JSON Schema, PubSub, etc.
Go
10
star
47

tree-sitter-cel

Tree sitter grammar for the Common Expression Language (CEL)
C
9
star
48

wellknowntypes

All the Well-Known Types stored in per-version directories.
Go
9
star
49

connect-envoy-demo

Demonstration of how to use the new Connect-gRPC Envoy filter, available in Envoy v1.26+.
Go
8
star
50

reflect-proto

Protobuf reflection API.
7
star
51

knit-proto

Protocol definition for Knit
6
star
52

bufisk

Bazelisk, but for Buf. A user-friendly launcher for Buf.
Go
6
star
53

protobuf-language-spec

Comprehensive language specification for Protocol Buffers
5
star
54

knit-demo

An example service built with Knit
Go
4
star
55

jest-environment-jsdom

A modern jsdom test environment for Jest
TypeScript
4
star
56

tools

A collection of tools written at Buf.
JavaScript
4
star
57

base-workflows

Shared Github Actions for BufBuild Organization.
2
star
58

confluent-proto

Proto definitions for integrating Confluent Schema Registry with the BSR
Makefile
1
star
59

.github

1
star