Skip to content

Implement predefined field constraints #246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Sep 17, 2024
Merged

Implement predefined field constraints #246

merged 25 commits into from
Sep 17, 2024

Conversation

jchadwick-buf
Copy link
Member

@jchadwick-buf jchadwick-buf commented Sep 4, 2024

  • Renames buf.validate.priv.FieldConstraints to buf.validate.PredefinedConstraint
  • Renames buf.validate.priv.field to buf.validate.predefined
  • Removes package buf.validate.priv to buf.validate
  • Merges buf/validate/expression.proto to buf/validate/validate.proto
  • Removes //proto/protovalidate/buf/validate:expression_proto
  • Switches buf/validate/validate.proto from proto3 syntax to proto2 syntax to enable usage of extensions
  • Adds extension 1000 to max to each ...Rules message
  • Adds test cases for valid and invalid predefined constraints of every possible rule type for both proto2 and protobuf edition 2023
  • Added the CEL constant rule that can be used by predefined constraints to refer to themselves specifically.

A predefined constraint in this schema looks like this:

edition = "2023";

package example.v1;

import "buf/validate/validate.proto";

extend buf.validate.StringRules {
  bool valid_path = 1162 [
    (buf.validate.predefined).cel = {
      id: "string.valid_path"
      expression: "!rule && !this.matches('^([^/.][^/]?|[^/][^/.]|[^/]{3,})(/([^/.][^/]?|[^/][^/.]|[^/]{3,}))*$') ? 'not a valid path: `%s`'.format([this]) : ''"
    }
  ];
}

message File {
  string path = 1 [(buf.validate.field).string.(valid_path) = true];
}

This is a breaking change.

@jchadwick-buf jchadwick-buf changed the title Implement shared field rules Implement shared field constraints Sep 4, 2024
@rodaine rodaine added Feature New feature or request Breaking Change Describes a breaking change to the protovalidate API labels Sep 4, 2024
@rodaine rodaine linked an issue Sep 4, 2024 that may be closed by this pull request
@jchadwick-buf
Copy link
Member Author

After merging main back in, it looks like there is a new lint error:

Error: Enum option "allow_alias" on enum "Ignore" must be false.

After some inspection it appears this may have to do with Buf v1.40.0, as the same code seems to have been passing in Buf v1.39. Further inspection may be needed. (In any case, it doesn't have anything to do with the changes in this PR as best as I can ascertain.)

rodaine
rodaine previously approved these changes Sep 6, 2024
@rodaine rodaine dismissed their stale review September 6, 2024 16:05

see comments

@oliversun9
Copy link
Contributor

After merging main back in, it looks like there is a new lint error:

Error: Enum option "allow_alias" on enum "Ignore" must be false.

After some inspection it appears this may have to do with Buf v1.40.0, as the same code seems to have been passing in Buf v1.39. Further inspection may be needed. (In any case, it doesn't have anything to do with the changes in this PR as best as I can ascertain.)

I think this is a CLI bug, I will take a look.

@rodaine
Copy link
Member

rodaine commented Sep 6, 2024

@oliversun9 @jchadwick-buf once the CLI issue is patched (and released) let's get that in here (or a separate pr that's merged in) as well as fixing the deprecation warnings. I'll merge bypass once the only remaining issue is the breaking change detection.

@oliversun9
Copy link
Contributor

@oliversun9 @jchadwick-buf once the CLI issue is patched (and released) let's get that in here (or a separate pr that's merged in) as well as fixing the deprecation warnings. I'll merge bypass once the only remaining issue is the breaking change detection.

I have a CLI PR up for the fix, but one thing you could do to avoid waiting on the CLI fix to land is to break the ignore comment into two lines, i.e.

// buf:lint:ignore ENUM_NO_ALLOW_ALIAS
// allowance for deprecations. TODO: remove pre-v1.

@jchadwick-buf
Copy link
Member Author

jchadwick-buf commented Sep 9, 2024

@oliversun9 Thanks for the quick fix! It worked, so no need to workaround this. (I did contemplate doing the workaround but it's probably for the best to not merge a PR this big on a Friday afternoon anyways.) I'll try to get all of the ducks in a row here and if there are no objections I'll get this merged and get the runtime PRs under review.

There's definitely more stuff that people will want (rules on arbitrary types, shared message constraints, recursively applying constraints) but I think this is a good opportunity to launch-and-iterate.

Thanks for all of the help, feedback, reviews, etc.!

>
> Extension numbers may be from 1000 to 536870911, inclusive. Values from 1000
> to 49999 are reserved for [Protobuf Global Extension Registry][1] entries, and
> values from 50000 to 536870911 are reserved for randomly-generated integers.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't actually true. 50000-99999 are reserved for internal use for organizations. The random number thing is actually a somewhat-uncomfortable recommendation for me - I get the birthday paradox, but I'm not thrilled that this is what we're recommending people do. I would prefer saying "choose a number that won't conflict" and leave this up to the user.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I was mistaken on the ranges somehow, that's what I get for not actually checking.

I agree we shouldn't recommend it, so I've adjusted the documentation altogether:

  • Fixed the ranges.
  • No longer explicitly recommends randomly-generated integers
  • Discourages use of 100000....536870911 for publicly-consumed schemas at all, due to risk of conflicts
  • But keeping a suggestion from Miguel, I kept a note that suggests using a high-quality random source if the user decides to use a randomly generated integer anyway, under the belief that it's better if they do that versus merely choose arbitrary numbers.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Remove random number generation references
  • Coalesce extension ranges

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

`buf.validate.FloatRules`, as follows:

```proto
import "buf/validate/shared/constraints.proto";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that these docs no longer match impl

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation has been updated.

// `Violations` is a collection of `Violation` messages. This message type is returned by
// protovalidate when a proto message fails to meet the requirements set by the `Constraint` validation rules.
// Each individual violation is represented by a `Violation` message.
message Violations {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't expect these to be intermixed in order with the *Constraints definitions. These should likely be at the bottom of the file

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved Violations and Violation to end of file.

// constraints that can then/ be set on field options of other messages to
// apply reusable field constraints.
//
// When using randomly generated numbers, please use a high-quality source of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't understand why we are referencing random number generation in the docs - I've read the comments, but this doesn't seem like a good idea.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, references to random number generation are just removed.

// messages to apply reusable field constraints.
//
// [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md
extensions 1000 to 99999;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we're splitting up the extension ranges into two sections - I've never seen that before. In the absence of a strong reason, coalesce.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was suggested here and seemed reasonable enough to me. Either way, it's coalesced again now.

@rodaine
Copy link
Member

rodaine commented Sep 17, 2024

:shipit: Thanks @jchadwick-buf! Herculean effort.

@jchadwick-buf jchadwick-buf merged commit 81eafa5 into bufbuild:main Sep 17, 2024
3 of 4 checks passed
@jchadwick-buf jchadwick-buf deleted the shared-field-rules branch September 17, 2024 15:24
jchadwick-buf added a commit to bufbuild/protovalidate-cc that referenced this pull request Sep 23, 2024
Like protovalidate-go and protovalidate-java, we need to adjust the code
to handle dynamic descriptor sets more robustly, since we need to jump
between resolving the protovalidate standard rules and the predefined
rule extensions. This necessitates adding a couple of additions to the
API surface, namely `ValidatorFactory::SetMessageFactory` and
`ValidatorFactory::SetAllowUnknownFields`, which controls instantiation
of unknown dynamic types and whether or not to ignore unresolved rules,
respectively. Like other protovalidate runtimes, we will default to
failing compilation when unknown predefined rules are encountered. This
should not break existing users but will prevent silent incorrect
behavior.

TODO:
- [x] Skip reparse when there are no empty fields—this way we can
avoid pessimizing the common case
- [x] Add an option to fail when unknown rule fields are unable to be
resolved.
- [x] Update for protobuf changes in
bufbuild/protovalidate#246.

This will depend on bufbuild/protovalidate#246.
jchadwick-buf added a commit to bufbuild/protovalidate-python that referenced this pull request Sep 25, 2024
jchadwick-buf added a commit to bufbuild/protovalidate-java that referenced this pull request Sep 26, 2024
- Adds the ability to specify an `ExtensionRegistry` _and_ a
`TypeRegistry` for resolving protobuf messages. Ordinarily, only a
`TypeRegistry` would necessarily be needed. However, we need to be able
to resolve extensions defined in file descriptor sets we don't control,
which means we need to be able to reparse _to and from_ the user's
descriptors in the worst case: _to_ the user's descriptors to get the
extended rule message (whose message type descriptors may have a
different hashcode and thus may not resolve using just an
`ExtensionRegistry` alone) and back _from_ the user's descriptors in
order to parse the `priv`/`shared` field.
- Refactors some of the code around reparsing and extensions in general:
- Reparsing options for protovalidate built-ins will always use a static
extension registry.
- Adds the `rule` variable.
- Some refactoring is done around the individual rule compilation, since
the code was getting a bit unwieldy.
- Updates the conformance runner to generate an `ExtensionRegistry` and
a `TypeRegistry`. This enables the conformance runner to pass both the
old conformance test suite and the new one, regardless of whether the
proto descriptors match up.

TODO:
- [x] Update to new version of protovalidate protos when they are
merged.

This will depend on bufbuild/protovalidate#246.
oguzhand95 added a commit to cerbos/cerbos-sdk-php that referenced this pull request Apr 8, 2025
The code generator for the php language doesn't work with `proto2`. We
were able to generate php code required in the SDK until
[protovalidate](https://github.com/bufbuild/protovalidate) migrated from
`proto3` to `proto2` in bufbuild/protovalidate#246.

Communicating with the [buf team](https://github.com/bufbuild), I've
learned a new feature `exclude_types` to be introduced which basically
excludes any types you specified with it.

Updated `buf.gen.yaml` with `exclude_types`:
```yaml
version: v2

plugins:
  - remote: buf.build/protocolbuffers/php:v30.2
    out: ./src
    exclude_types:
      - buf.validate

  - remote: buf.build/grpc/php:v1.71.0
    out: ./src
    exclude_types:
      - buf.validate
```

Also this PR updates some dependencies and introduces support for `php
8.4` while removing support for `php 8.3`.

Signed-off-by: Oğuzhan Durgun <oguzhandurgun95@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Breaking Change Describes a breaking change to the protovalidate API Feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BREAKING CHANGE] Reusing Custom Validation Rules
4 participants