Skip to content
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,107 @@ 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/protovalidate-check) 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/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
Expand All @@ -151,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/
Loading