Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/envoyproxy/protoc-gen-validate
Protocol Buffer Validation - Being replaced by github.com/bufbuild/protovalidate
https://github.com/envoyproxy/protoc-gen-validate
constraints protoc protoc-plugin protocol-buffers validation
Last synced: about 2 months ago
JSON representation
Protocol Buffer Validation - Being replaced by github.com/bufbuild/protovalidate
- Host: GitHub
- URL: https://github.com/envoyproxy/protoc-gen-validate
- Owner: bufbuild
- License: apache-2.0
- Created: 2017-10-03T22:51:17.000Z (about 7 years ago)
- Default Branch: main
- Last Pushed: 2024-04-10T18:26:10.000Z (8 months ago)
- Last Synced: 2024-04-14T18:28:45.892Z (8 months ago)
- Topics: constraints, protoc, protoc-plugin, protocol-buffers, validation
- Language: Go
- Homepage: https://github.com/bufbuild/protovalidate
- Size: 13.4 MB
- Stars: 3,630
- Watchers: 203
- Forks: 569
- Open Issues: 12
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Code of conduct: .github/CODE_OF_CONDUCT.md
Awesome Lists containing this project
- awesome-list - protoc-gen-validate
- grpc-protos - envoyproxy/protoc-gen-validate - gen-validate) | core (Repositories)
README
# [![](./.github/buf-logo.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)> [!IMPORTANT]
> protoc-gen-validate (PGV) has reached a stable state and is in maintenance mode.
>
> We recommend that new and existing projects transition to using [`protovalidate`][pv].
>
> Read [our blog post][pv-announce] if you want to learn more about the limitations of protoc-gen-validate and
> how we have designed [`protovalidate`][pv] to be better.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:```protobuf
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}];
}
}
```Executing `protoc` with PGV and the target language's default plugin will
create `Validate` methods on the generated types:```go
p := new(Person)err := p.Validate() // err: Id must be greater than 999
p.Id = 1000err = p.Validate() // err: Email must be a valid email address
p.Email = "[email protected]"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.4err = 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](https://github.com/bufbuild/protoc-gen-validate/releases) and unarchive them and add plugins into `$PATH`.
#### Build from source
```sh
# 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 https://github.com/bufbuild/protoc-gen-validate.git
# 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`:```sh
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](https://github.com/golang/protobuf#packages-and-input-paths)
or [parameters](https://github.com/golang/protobuf#parameters) for more
information.There's also support for the `module=example.com/foo`
flag [described here](https://developers.google.com/protocol-buffers/docs/reference/go-generated#invocation)
.With newer Buf CLI versions (>v1.9.0), you can use the new plugin key instead of using the `protoc` command directly:
```
# buf.gen.yamlversion: v1
plugins:
- plugin: buf.build/bufbuild/validate-go
out: gen
``````
# proto/buf.yamlversion: 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.```xml
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}
```
```gradle
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"
}
}
}
}
``````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](https://docs.python.org/3/library/functions.html#exec).
An LRU-cache improves performance by
storing generated functions per descriptor.The python package is available
on [PyPI](https://pypi.org/project/protoc-gen-validate).To run `validate()`, do the following:
```python
from entities_pb2 import Person
from protoc_gen_validate.validator import validate, ValidationFailedp = 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](validate/validate.proto) 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](rule_comparison.md) 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.
```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
uint32 x = 1 [(validate.rules).uint32 = {ignore_empty: true, gte: 200}];
```### Bools
- **const**: the field must be _exactly_ the specified value.
```protobuf
// 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.
```protobuf
// 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.```protobuf
// 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.```protobuf
// 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][re2] regular
expression. The included expression should elide any delimiters (ie, `/\d+/`
should just be `\d+`).```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
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.```protobuf
// 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.
```protobuf
// 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.```protobuf
// 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][re2] regular
expression. The included expression should elide any delimiters (ie, `/\d+/`
should just be `\d+`).```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
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.```protobuf
// 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
```protobuf
enum State {
INACTIVE = 0;
PENDING = 1;
ACTIVE = 2;
}
```- **const**: the field must be _exactly_ the specified value.
```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.
```protobuf
// 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```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
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```protobuf
// x must contain at least 3 KV pairs
map x = 1 [(validate.rules).map.min_pairs = 3];// x must contain between 5 and 10 KV pairs
map x = 1 [(validate.rules).map = {min_pairs: 5, max_pairs: 10}];// x must contain exactly 7 KV pairs
map 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.```protobuf
// all values in x must be set
map x = 1 [(validate.rules).map.no_sparse = true];
```- **keys**: this rule specifies constraints that are applied to the keys in the
field.```protobuf
// x's keys must all be negative
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.```protobuf
// x must contain strings of at least 3 characters
map x = 1 [(validate.rules).map.values.string.min_len = 3];// x must contain Persons but doesn't validate them
map 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.```protobuf
map x = 1 [(validate.rules).map = {ignore_empty: true, values: {string: {min_len: 3}}}];
```### Well-Known Types (WKTs)
A set of [WKTs][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.```protobuf
// 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):
```protobuf
// 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
```protobuf
// 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.```protobuf
// 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
```protobuf
// x cannot be unset
google.protobuf.Duration x = 1 [(validate.rules).duration.required = true];
```- **const**: the field must be _exactly_ the specified value.
```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
// 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
```protobuf
// x cannot be unset
google.protobuf.Timestamp x = 1 [(validate.rules).timestamp.required = true];
```- **const**: the field must be _exactly_ the specified value.
```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
// 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.```protobuf
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.```protobuf
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.```protobuf
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][pg*] 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][protoc-source], the
provided [releases][protoc-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`](/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](/Dockerfile) for consistent development tooling
and CI. The main entrypoint is `make` with `build` as the default target.```sh
# 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
```[buf]: https://buf.build
[protoc-source]: https://github.com/google/protobuf
[protoc-releases]: https://github.com/google/protobuf/releases
[pg*]: https://github.com/bufbuild/protoc-gen-star
[re2]: https://github.com/google/re2/wiki/Syntax
[wkts]: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf
[pv]: https://github.com/bufbuild/protovalidate
[pv-announce]: https://buf.build/blog/protoc-gen-validate-v1-and-v2/