-
Notifications
You must be signed in to change notification settings - Fork 36
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
Implement predefined field constraints #246
Implement predefined field constraints #246
Conversation
… shared-field-rules
… shared-field-rules
After merging
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. |
@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.
|
@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.! |
@@ -55,6 +53,81 @@ extend google.protobuf.FieldOptions { | |||
// Rules specify the validations to be performed on this field. By default, | |||
// no validation is performed against a field. | |||
optional FieldConstraints field = 1159; | |||
|
|||
// Specifies a shared field constraint rule. This option can be specified on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the docs here - the types are the same, why can I not share something specified in field
? Why is this named shared_field
? I'm sure there are answers to all of this, but it's not clear to me how to use these two extensions from the documentation here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did propose that, and even implemented it in protovalidate-go
before the current implementation. As a result of many discussions, I wound up backtracking to something very close to @rodaine's original proposal instead. Personally I still like the idea of unifying it all, but I don't think I really convinced anyone else of it.
The main points are:
-
There is concern that using the same extension for shared rules and custom rules would be confusing.
-
It's confusing to understand when shared rules would apply. For example, consider this:
extend buf.validate.FloatRules { bool is_zero [(buf.validate.field).float.const = 0] } message MyMessage { // `is_zero` applies, since it is set. float number_1 = 1 [(buf.validate.field).float.(is_zero) = true]; // `is_zero` still applies, since it is set. Extensions always have field presence. float number_2 = 2 [(buf.validate.field).float.(is_zero) = false]; }
This isn't a huge problem for the current version because you can do this:
extend buf.validate.FloatRules { bool is_zero [(buf.validate.shared_field).cel = "rule ? this == 0 : true"] }
-
Moving the standard rules to use the same mechanism as custom constraints will require making changes to how custom constraints work in each runtime to allow rule values to be plumbed through.
If you would prefer a version that eliminates the need for a SharedFieldConstraints
type and allows using standard constraints recursively, I do have some work already done on that front and can switch gears.
I discussed a design that would work this way here: #246 (comment) - but it would need to change at least somewhat, since we can't just blindly proto.Merge
everything if we want to be able to e.g. bind rules
/rule
differently per CEL expression based on where it is in the chain of options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rodaine I'd love to sync on this for 5 minutes pre-merge
@jchadwick-buf my main comment here is that regardless of design (and even assuming that this existing design stays), the comments/documentation are not clear to me as a user. I'm reading the comments and am not sure how to use protovalidate - this is a critical thing to tackle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand what you mean, I'll try to improve the comments at least. I do worry that the reason it's hard to explain is because the design is actually a bit confusing, so it would probably be best if we really did make sure that this is what we want to go with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed a probable huge part of the confusion, which is that I have had a typo here. I need to switch the second one to SharedFieldConstraints
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now it is PredefinedConstraints
and predefined
. I'm leaving this comment unresolved just in case I've screwed up any details from the meeting.
docs/shared-constraints.md
Outdated
> | ||
> 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
docs/shared-constraints.md
Outdated
`buf.validate.FloatRules`, as follows: | ||
|
||
```proto | ||
import "buf/validate/shared/constraints.proto"; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Thanks @jchadwick-buf! Herculean effort. |
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.
- 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.
buf.validate.priv.FieldConstraints
tobuf.validate.PredefinedConstraint
buf.validate.priv.field
tobuf.validate.predefined
buf.validate.priv
tobuf.validate
buf/validate/expression.proto
tobuf/validate/validate.proto
//proto/protovalidate/buf/validate:expression_proto
buf/validate/validate.proto
fromproto3
syntax toproto2
syntax to enable usage of extensionsextension 1000 to max
to each...Rules
messagerule
that can be used by predefined constraints to refer to themselves specifically.A predefined constraint in this schema looks like this:
This is a breaking change.