Introduce custom constraints to extend the smithy validation rules#1171
Introduce custom constraints to extend the smithy validation rules#1171Gravewalker666 wants to merge 7 commits intosmithy-lang:mainfrom
Conversation
b597a16 to
00b7074
Compare
00b7074 to
3163773
Compare
363d10c to
21a9497
Compare
21a9497 to
f815d7a
Compare
| } | ||
| } | ||
|
|
||
| record CustomValidationFailure(String path, Schema schema, String message) implements ValidationError {} |
There was a problem hiding this comment.
I'm not sure this needs to be defined here, ValidationError is an open public interface that custom integrations can implement and define their own errors in their packages? Feels like redundant to me
| for (var error: validationErrors) { | ||
| addError(error); |
There was a problem hiding this comment.
It might worth adding a new addErrors method as adding multiple objects should be faster than adding then one by one. Also, having it this way might lead to the situation where, say, you have maxAllowedErrors=1 but custom validator returned 2 errors (and these are the only errors during the validation) - we will add only first but the second one has been generated already so maybe we should still add it? It feels like maxAllowedErrors is more like a circuit breaker (to stop further validation) but if the validation is done already, why not just add these errors?
| } | ||
|
|
||
| private void applyCustomConstraints(Schema schema, Object value) { | ||
| if (!CustomConstraintProvider.HAS_CUSTOM_CONSTRAINTS) { |
There was a problem hiding this comment.
this feels a bit odd to me - we have a static field but we operate on a instance basis. Should either this to be related to an instance of CustomConstraintProvider or other methods (e.g. provide, key) to be static in the class?
| return; | ||
| } | ||
| for (var rule: customConstraints) { | ||
| var validationErrors = rule.validate(schema, value, createPath()); |
There was a problem hiding this comment.
createPath should be created up front and reused. Or better still, we could give the validator a Supplier instead of eagerly computing the path.
|
|
||
| checkListLength(schema, count); | ||
| } | ||
| applyCustomConstraints(schema, state); |
There was a problem hiding this comment.
Technically, state passed to writeList and writeMap don't have to be the list/map being written (e.g., it could be streaming, or just canned content). The "state" value just acts as a carrier to avoid needing a closure that references up-values. That makes custom list and map validation tricky.
| } | ||
| for (var rule: customConstraints) { | ||
| var validationErrors = rule.validate(schema, value, createPath()); | ||
| for (var error: validationErrors) { |
There was a problem hiding this comment.
| for (var error: validationErrors) { | |
| for (var error : validationErrors) { |
|
|
||
| @Override | ||
| public List<CustomConstraint> provide(Schema schema) { | ||
| var rulesForThisSchema = new ArrayList<CustomConstraint>(); |
There was a problem hiding this comment.
This seems heavy. It adds a list to every schema we validate. I think instead of this, we could have CustomConstraint return an EnumSet<ShapeType> appliesTo(). Then we'd have a List<CustomConstraint>[] on the class, sized to the number of shape types. Then check if there's an entry per/shape being validated. Then there's no SchemaExtension and no need to punch an array/schema onto all schemas.
There was a problem hiding this comment.
Then we'd have a List[] on the class, sized to the number of shape types
@mtdowling does it mean that there would be only one custom constraint possible per shape type? If so, which approach you'd choose to select which one should apply when there are multiple options? I'm curious if in this case the overall approach should be to pass "constraint per shape type" map to the validator constructor to avoid ambiguity when using SPI...
There was a problem hiding this comment.
I think we should do away with the idea of per/shape custom validators, and instead have per/type custom validators (which there could be many per/type). Maybe with support for a wildcard which matches any type. If you need shape-specific validators (which would likely be rare), then that filtering can be done in validate by inspecting the Schema.
Issue #, if available:
#1089
Description of changes:
CustomConstraintinterface for extensible validation rules via ServiceLoaderValidator.CustomConstraintProviderto discover and register custom constraints as schema extensionsapplyCustomConstraints()into Validator.ShapeValidator to apply custom rules after standard validationValidationError.CustomValidationFailurerecord for custom constraint violationsBenchmark numbers:
A record of benchmark runs I've done along the way is documented here (non-complete): https://pastebin.com/GTuJKkQN
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.