From c8b244fe33c3c635330e2007dd94fbf5286662f2 Mon Sep 17 00:00:00 2001 From: Elliot Jackson <13633636+ElliotMJackson@users.noreply.github.com> Date: Wed, 26 Oct 2022 10:03:34 -0400 Subject: [PATCH] Improve Development section of readme (#689) Highlighted by our valued contributor @HaloWorld in #606, the `development` section of our readme needs some changes. This really does need a lot more work but should do the trick for now. --- .dockerignore | 1 + README.md | 407 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 273 insertions(+), 135 deletions(-) diff --git a/.dockerignore b/.dockerignore index f98ec8829..434978f75 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ .git/ .idea/ +.bin/ diff --git a/README.md b/README.md index 13eb49d3f..14ff8417b 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,16 @@ ![Release](https://img.shields.io/github/v/release/bufbuild/protoc-gen-validate?include_prereleases) ![Slack](https://img.shields.io/badge/slack-buf-%23e01563) -*This project is currently in **alpha**. The API should be considered unstable and likely to change* +*This project is currently in **alpha**. The API should be considered unstable +and likely to change* -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. +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: +Developers import the PGV extension and annotate the messages and fields in +their proto files with constraint rules: ```protobuf syntax = "proto3"; @@ -20,25 +25,26 @@ package examplepb; import "validate/validate.proto"; message Person { - uint64 id = 1 [(validate.rules).uint64.gt = 999]; + 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, - }]; + 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 }]; + 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: +Executing `protoc` with PGV and the target language's default plugin will +create `Validate` methods on the generated types: ```go p := new(Person) @@ -47,7 +53,7 @@ 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" +p.Email = "example@bufbuild.com" err = p.Validate() // err: Name must match pattern '^[^\d\s]+( [^\d\s]+)*$' p.Name = "Protocol Buffer" @@ -69,7 +75,8 @@ err = p.Validate() // err: nil - `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. +- **Only `proto3` syntax is currently supported.** `proto2` syntax support is + planned. ### Installation @@ -77,11 +84,17 @@ Installing PGV can currently only be done from source: ```sh # fetches this repo into $GOPATH -go get -d github.com/envoyproxy/protoc-gen-validate +go get -d github.com/bufbuild/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. +> #### 💡 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 `bufbuild` module path. ``` git clone github.com/bufbuild/protoc-gen-validate @@ -91,44 +104,60 @@ 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. +- **`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`: +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 ${GOPATH}/src \ - -I ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate \ + -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. + +- `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:.`. 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. +PGV requires no additional runtime dependencies from the existing generated +code. -There's also support for the `module=example.com/foo` flag [described here](https://developers.google.com/protocol-buffers/docs/reference/go-generated#invocation). +**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:.`. 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) +. #### Java -Java generation is integrated with the existing protobuf toolchain for java projects. For Maven projects, add the +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 @@ -138,36 +167,40 @@ following to your pom.xml or build.gradle. - - - 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} - - - - - + + + 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} + + + + + + ``` @@ -212,13 +245,17 @@ serverBuilder.addService(ServerInterceptors.intercept(svc, new ValidatingServerI #### 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). +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: -To run `validate()`, do the following: ```python from entities_pb2 import Person from protoc_gen_validate.validator import validate, ValidationFailed @@ -230,17 +267,23 @@ except ValidationFailed as err: print(err) ``` -You can view what code has been generated by using the `print_validate()` function. +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. +[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. +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. +> 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. @@ -249,7 +292,8 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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. +- **lt/lte/gt/gte**: these inequalities (`<`, `<=`, `>`, `>=`, respectively) + allow for deriving ranges in which the field must reside. ```protobuf // x must be less than 10 @@ -262,14 +306,16 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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. + 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. +- **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 @@ -279,7 +325,10 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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. +- **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 unint32 x = 1 [(validate.rules).uint32 = {ignore_empty: true, gte: 200}]; @@ -306,7 +355,10 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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. +- **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 @@ -319,7 +371,8 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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. +- **min_bytes/max_bytes**: these rules constrain the number of bytes in the + field. ```protobuf // x must be at most 15 bytes long @@ -329,14 +382,18 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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+`). +- **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. +- **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" @@ -358,7 +415,8 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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. +- **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" @@ -368,13 +426,19 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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. +- **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. +- **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) @@ -418,7 +482,8 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s ### Bytes -> Literal values should be expressed with strings, using escaping where necessary. +> Literal values should be expressed with strings, using escaping where +> necessary. - **const**: the field must be _exactly_ the specified value. @@ -430,7 +495,8 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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. +- **len/min_len/max_len**: these rules constrain the number of bytes in the + field. ```protobuf // x must be exactly 3 bytes @@ -443,14 +509,17 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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+`). +- **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. +- **prefix/suffix/contains**: the field must contain the specified byte sequence + in an optionally explicit location. ```protobuf // x must begin with "\x99" @@ -463,7 +532,8 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s bytes x = 1 [(validate.rules).bytes.contains = "baz"]; ``` -- **in/not_in**: these two rules permit specifying allow/denylists for the values of a field. +- **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" @@ -473,13 +543,19 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s 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. +- **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. +- **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 @@ -496,15 +572,16 @@ Check the [constraint rule comparison matrix](rule_comparison.md) for language-s ### Enums -> All literal values should use the numeric (int32) value as defined in the enum descriptor. +> 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; + PENDING = 1; + ACTIVE = 2; } ``` @@ -515,14 +592,16 @@ enum State { State x = 1 [(validate.rules).enum.const = 2]; ``` -- **defined_only**: the field must be one of the specified values in the enum descriptor. +- **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. +- **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) @@ -534,15 +613,18 @@ enum State { ### 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 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; + Person x = 1; ``` -- **skip**: this rule specifies that the validation rules of this field should not be evaluated. +- **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. @@ -561,7 +643,8 @@ Person x = 1; ### Repeated -- **min_items/max_items**: these rules control how many elements are contained in the field +- **min_items/max_items**: these rules control how many elements are contained + in the field ```protobuf // x must contain at least 3 elements @@ -574,14 +657,17 @@ Person x = 1; 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. +- **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. +- **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 @@ -591,7 +677,10 @@ Person x = 1; 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. +- **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}}}]; @@ -599,7 +688,8 @@ Person x = 1; ### Maps -- **min_pairs/max_pairs**: these rules control how many KV pairs are contained in this field +- **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 @@ -612,21 +702,25 @@ Person x = 1; 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. +- **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. +- **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. +- **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 @@ -636,7 +730,10 @@ Person x = 1; 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. +- **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}}}]; @@ -644,22 +741,26 @@ Person x = 1; ### Well-Known Types (WKTs) -A set of [WKTs][wkts] are packaged with protoc and common message patterns useful in many domains. +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. +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]; + 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]; } +message X {google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, (validate.rules).message.required = true];} ``` #### Anys @@ -671,7 +772,9 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, 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. +- **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 @@ -700,7 +803,8 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, }]; ``` -- **lt/lte/gt/gte**: these inequalities (`<`, `<=`, `>`, `>=`, respectively) allow for deriving ranges in which the field must reside. +- **lt/lte/gt/gte**: these inequalities (`<`, `<=`, `>`, `>=`, respectively) + allow for deriving ranges in which the field must reside. ```protobuf // x must be less than 10s @@ -716,7 +820,8 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, }]; ``` - Inverting the values of `lt(e)` and `gt(e)` is valid and creates an exclusive range. + 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) @@ -726,7 +831,8 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, }]; ``` -- **in/not_in**: these two rules permit specifying allow/denylists for the values of a field. +- **in/not_in**: these two rules permit specifying allow/denylists for the + values of a field. ```protobuf // x must be either 0s or 1s @@ -761,7 +867,8 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, }]; ``` -- **lt/lte/gt/gte**: these inequalities (`<`, `<=`, `>`, `>=`, respectively) allow for deriving ranges in which the field must reside. +- **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 @@ -777,7 +884,8 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, }]; ``` - Inverting the values of `lt(e)` and `gt(e)` is valid and creates an exclusive range. + 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) @@ -787,13 +895,16 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, }]; ``` -- **lt_now/gt_now**: these inequalities allow for ranges relative to the current time. These rules cannot be used with the absolute rules above. +- **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. +- **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 @@ -808,7 +919,8 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, ### Message-Global -- **disabled**: All validation rules for the fields on a message can be nullified, including any message fields that support validation themselves. +- **disabled**: All validation rules for the fields on a message can be + nullified, including any message fields that support validation themselves. ```protobuf message Person { @@ -822,7 +934,8 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, } ``` -- **ignored**: Don't generate a validate method or any related validation code for this message. +- **ignored**: Don't generate a validate method or any related validation code + for this message. ```protobuf message Person { @@ -838,7 +951,9 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, ### 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. +- **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 { @@ -853,21 +968,30 @@ message X { google.protobuf.Int32Value age = 1 [(validate.rules).int32.gt = -1, ## Development -PGV is written in Go on top of the [protoc-gen-star][pg*] framework and compiles to a standalone binary. +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. +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 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 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 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. +- **`make harness`**: executes the test-cases against each language's test + harness. ### Run all tests under Bazel @@ -879,26 +1003,39 @@ bazel test //tests/... ### Docker -PGV comes with a [Dockerfile](/Dockerfile) for consistent development tooling and CI. The main entrypoint is `make` with `quick` as the default target. This repo should be volumed into `/go/src/github.com/envoyproxy/protoc-gen-validate` for the proper behavior. +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 lyft/protoc-gen-validate . +docker build -t bufbuild/protoc-gen-validate . + +# executes the default make target: build +docker run --rm \ + bufbuild/protoc-gen-validate -# executes the default make target: quick +# executes the 'ci' make target docker run --rm \ - -v $(PWD):/go/src/github.com/envoyproxy/protoc-gen-validate \ - lyft/protoc-gen-validate + bufbuild/protoc-gen-validate ci # 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 + bufbuild/protoc-gen-validate build generate-testdata + +# 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 ``` [protoc-source]: https://github.com/google/protobuf + [protoc-releases]: https://github.com/google/protobuf/releases -[pg*]: https://github.com/lyft/protoc-gen-star + +[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