From 41cd0262ab6855104785d15617c9f35214762a45 Mon Sep 17 00:00:00 2001 From: Alex Eagan Date: Mon, 23 Mar 2026 14:22:10 -0400 Subject: [PATCH 1/4] Document validate.proto invariants in CONTRIBUTING --- .github/CONTRIBUTING.md | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e64ca580..9f0237e1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -132,6 +132,65 @@ By keeping performance and resource management in mind throughout the development process, we can ensure `protovalidate` remains efficient and responsive, even as we add new features and fix bugs. +## validate.proto Invariants + +`validate.proto` has several structural invariants that must be upheld by +anyone modifying it — whether adding a new rule, a new type, or a new `*Rules` +message. A [static checker](../tools/internal/staticcheck) enforces these +automatically. Run `make lint-proto` to verify them before opening a pull +request. + +### Field-to-rules type mapping + +Each field in the `FieldRules.type` oneof corresponds to exactly one `*Rules` +message and one or more protobuf field kinds. For example, the `string` field +in the `type` oneof refers to `StringRules`, which applies only to fields of +kind `string`. + +This mapping must be maintained in two places: `validate.proto` itself, and +every runtime implementation, which must apply the correct `*Rules` message +based on the field's kind. The static checker validates the proto side; runtime +conformance tests must cover the implementation side. + +### Field name consistency + +Each field in `FieldRules.type` must be named to match its corresponding +`*Rules` message: the lowercase `*Rules` name with the `Rules` suffix removed. +For example, `StringRules` maps to `string` and `DurationRules` maps to +`duration`. + +### Extension ranges + +All `*Rules` messages must declare `extensions 1000 to max;` so that users can +define predefined rules. The one exception is `AnyRules`, which must _not_ have +an extension range. + +### Example fields + +All `*Rules` messages must include a `repeated` example field typed to match +the rule's target type. The example field: + +1. Must be a `repeated` field with the element type matching the rule's target type. +2. Must have a `(predefined).cel` annotation with expression `"true"` and a rule ID ending in `.example`. +3. Must not have a `message` set in its `(predefined).cel` annotation. + +The exceptions are `AnyRules`, `RepeatedRules`, and `MapRules`, which must not +have example fields. + +### CEL rules on every rule field + +Every rule field in a `*Rules` message must have at least one `(predefined).cel` +annotation. The static checker compiles every expression against the correct type +environment and verifies the return type: fields without a `message` return +`string` (empty means pass), and fields with a `message` return `bool`. + +Rule IDs must be globally unique and prefixed with the name of the corresponding +`FieldRules.type` oneof field (e.g. `string.min_len`, `duration.gte`). + +A small set of fields are exempt because they are validated directly by runtime +implementations. The canonical list is in +[`tools/internal/staticcheck/known.go`](../tools/internal/staticcheck/known.go). + ## Questions? If you have any questions, please don't hesitate to create an issue, and we'll From 9290967bc89ffb62fd916f9f1b5f3b1dcf15dffd Mon Sep 17 00:00:00 2001 From: Alexandra Eagan <32371277+EaganVP@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:04:24 -0400 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Timo Stamm --- .github/CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9f0237e1..c5ae9ee9 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -136,7 +136,7 @@ responsive, even as we add new features and fix bugs. `validate.proto` has several structural invariants that must be upheld by anyone modifying it — whether adding a new rule, a new type, or a new `*Rules` -message. A [static checker](../tools/internal/staticcheck) enforces these +message. A [static checker](../tools/internal/protovalidate-check) enforces these automatically. Run `make lint-proto` to verify them before opening a pull request. @@ -189,7 +189,7 @@ Rule IDs must be globally unique and prefixed with the name of the corresponding A small set of fields are exempt because they are validated directly by runtime implementations. The canonical list is in -[`tools/internal/staticcheck/known.go`](../tools/internal/staticcheck/known.go). +[`tools/internal/staticcheck/known.go`](../tools/internal/protovalidate-check/known.go). ## Questions? From e4b57b1670aa7fd4842f680a1b54e05e0837f3e4 Mon Sep 17 00:00:00 2001 From: Alex Eagan Date: Tue, 31 Mar 2026 14:19:33 -0400 Subject: [PATCH 3/4] Document the CEL environment for validate.proto rules --- .github/CONTRIBUTING.md | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c5ae9ee9..939178ca 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -191,6 +191,48 @@ A small set of fields are exempt because they are validated directly by runtime implementations. The canonical list is in [`tools/internal/staticcheck/known.go`](../tools/internal/protovalidate-check/known.go). +### CEL environment + +Every CEL expression in `validate.proto` runs inside a fixed environment +defined by protovalidate. +The static checker rejects any expression that references a function or +variable not declared in that environment. + +On top of CEL's standard library, the environment adds: + +**Extensions** + +The [CEL strings extension][cel-strings] is available, adding string-manipulation +functions beyond what standard CEL provides (e.g. `upperAscii`, `trim`, +`splitWithSizeLimit`). + +**Variables** + +| Name | Type | +|------|------| +| `now` | `google.protobuf.Timestamp` | +| `this` | Field value being validated | +| `rule` | Value of the specific rule field | +| `rules` | The full `*Rules` message for the field | + +**Functions** + +- Type conversions: `double`, `int`, `string`, and `list`. +- `bytes` overloads for the standard `contains`, `endsWith`, and `startsWith`. +- Custom protovalidate functions: `unique`, `isNan`, `isInf`, `isHostname`, + `isEmail`, `isIp`, `isIpPrefix`, `isUri`, `isUriRef`, `isHostAndPort`, + and `getField`. + +Full signatures and semantics are documented in the +[CEL extensions reference][cel-extensions-ref]. + +**Extending the environment** + +The environment is declared in +[`tools/internal/protovalidate-check/cel.go`](../tools/internal/protovalidate-check/cel.go). +Any contribution that introduces a new function or variable must add its +signature there. + ## Questions? If you have any questions, please don't hesitate to create an issue, and we'll @@ -210,3 +252,7 @@ working together to make `protovalidate` the best it can be. [file-feature-request]: https://github.com/bufbuild/protovalidate/issues/new?assignees=&labels=Feature&template=feature_request.md&title=%5BFeature+Request%5D [cel-spec]: https://github.com/google/cel-spec + +[cel-strings]: https://celbyexample.com/strings/ + +[cel-extensions-ref]: https://protovalidate.com/reference/cel_extensions/ From 45000087c2209a5c280f86ed58c31b4679b57a4e Mon Sep 17 00:00:00 2001 From: Alex Eagan Date: Tue, 31 Mar 2026 14:55:55 -0400 Subject: [PATCH 4/4] Trim CEL environment section to avoid duplicating cel.go --- .github/CONTRIBUTING.md | 38 +++++--------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 939178ca..9c70bb63 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -198,39 +198,13 @@ defined by protovalidate. The static checker rejects any expression that references a function or variable not declared in that environment. -On top of CEL's standard library, the environment adds: - -**Extensions** - -The [CEL strings extension][cel-strings] is available, adding string-manipulation -functions beyond what standard CEL provides (e.g. `upperAscii`, `trim`, -`splitWithSizeLimit`). - -**Variables** - -| Name | Type | -|------|------| -| `now` | `google.protobuf.Timestamp` | -| `this` | Field value being validated | -| `rule` | Value of the specific rule field | -| `rules` | The full `*Rules` message for the field | - -**Functions** - -- Type conversions: `double`, `int`, `string`, and `list`. -- `bytes` overloads for the standard `contains`, `endsWith`, and `startsWith`. -- Custom protovalidate functions: `unique`, `isNan`, `isInf`, `isHostname`, - `isEmail`, `isIp`, `isIpPrefix`, `isUri`, `isUriRef`, `isHostAndPort`, - and `getField`. - +On top of CEL's standard library, the environment adds several variables and +functions. Full signatures and semantics are documented in the [CEL extensions reference][cel-extensions-ref]. - -**Extending the environment** - -The environment is declared in -[`tools/internal/protovalidate-check/cel.go`](../tools/internal/protovalidate-check/cel.go). -Any contribution that introduces a new function or variable must add its +The environment itself is declared in +[`tools/internal/protovalidate-check/cel.go`](../tools/internal/protovalidate-check/cel.go); +any contribution that introduces a new function or variable must add its signature there. ## Questions? @@ -253,6 +227,4 @@ working together to make `protovalidate` the best it can be. [cel-spec]: https://github.com/google/cel-spec -[cel-strings]: https://celbyexample.com/strings/ - [cel-extensions-ref]: https://protovalidate.com/reference/cel_extensions/