-
Notifications
You must be signed in to change notification settings - Fork 64
Added support for inheritance hierarchies validation #369
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
base: develop
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,29 +15,6 @@ | |
| */ | ||
| package am.ik.yavi.builder; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.math.BigInteger; | ||
| import java.time.Instant; | ||
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.time.LocalTime; | ||
| import java.time.OffsetDateTime; | ||
| import java.time.Year; | ||
| import java.time.YearMonth; | ||
| import java.time.ZonedDateTime; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.Deque; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.LinkedList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.function.Consumer; | ||
| import java.util.function.Function; | ||
| import java.util.function.Predicate; | ||
| import java.util.function.Supplier; | ||
|
|
||
| import am.ik.yavi.constraint.BigDecimalConstraint; | ||
| import am.ik.yavi.constraint.BigIntegerConstraint; | ||
| import am.ik.yavi.constraint.BooleanConstraint; | ||
|
|
@@ -111,6 +88,29 @@ | |
| import am.ik.yavi.meta.YearMonthConstraintMeta; | ||
| import am.ik.yavi.meta.ZonedDateTimeConstraintMeta; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.math.BigInteger; | ||
| import java.time.Instant; | ||
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.time.LocalTime; | ||
| import java.time.OffsetDateTime; | ||
| import java.time.Year; | ||
| import java.time.YearMonth; | ||
| import java.time.ZonedDateTime; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.Deque; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.LinkedList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.function.Consumer; | ||
| import java.util.function.Function; | ||
| import java.util.function.Predicate; | ||
| import java.util.function.Supplier; | ||
|
|
||
| public class ValidatorBuilder<T> implements Cloneable { | ||
| private static final String DEFAULT_SEPARATOR = "."; | ||
|
|
||
|
|
@@ -732,6 +732,33 @@ public ValidatorBuilder<T> _doubleArray(ToDoubleArray<T> f, String name, | |
| return this.constraint(f, name, c, DoubleArrayConstraint::new); | ||
| } | ||
|
|
||
| /** | ||
| * @since 0.14.0 | ||
| */ | ||
| public <C extends T> ValidatorBuilder<T> constraintOnClass(Class<C> clazz, | ||
| Validator<C> cValidator) { | ||
| Validator<T> TValidator = new ValidatorBuilder<T>() | ||
| .nest(clazz::cast, clazz.getName(), cValidator).build(); | ||
|
|
||
| return constraintOnCondition(getClassConstraintCondition(clazz), TValidator); | ||
| } | ||
|
|
||
| /** | ||
| * @since 0.14.0 | ||
| */ | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These methods are quite advanced. Can you please add to the JavaDoc what use cases it is useful for and some sample code?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. absolutely |
||
| public <C extends T> ValidatorBuilder<T> constraintOnClass(Class<C> clazz, | ||
| ValidatorBuilderConverter<C> converter) { | ||
| ValidatorBuilderConverter<T> tConverter = tValidatorBuilder -> tValidatorBuilder | ||
| .nest(clazz::cast, clazz.getName(), converter); | ||
|
|
||
| return constraintOnCondition(getClassConstraintCondition(clazz), tConverter); | ||
| } | ||
|
|
||
| private <C extends T> ConstraintCondition<T> getClassConstraintCondition( | ||
| Class<C> classCondition) { | ||
| return (t, c) -> classCondition.isInstance(t); | ||
| } | ||
|
|
||
| /** | ||
| * @since 0.11.0 | ||
| */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| package am.ik.yavi.core; | ||
|
|
||
| import am.ik.yavi.User; | ||
| import am.ik.yavi.builder.ValidatorBuilder; | ||
| import am.ik.yavi.constraint.CharSequenceConstraint; | ||
| import org.junit.jupiter.api.TestInstance; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.Arguments; | ||
| import org.junit.jupiter.params.provider.MethodSource; | ||
|
|
||
| import java.util.function.Function; | ||
| import java.util.stream.Stream; | ||
|
|
||
| import static com.google.common.truth.Truth.assertThat; | ||
|
|
||
| public class ConstraintOnClassTest { | ||
|
|
||
| @ParameterizedTest | ||
| @MethodSource("provideValidators") | ||
| void testConstraintOnConditionClass(Validator<User> validator) { | ||
| User validAdmin = new Admin("admin123", "admin@gmail", 27, "yavi123"); | ||
| User invalidAdmin = new Admin("Niraz", "niraz@gmail", 23, "user"); | ||
|
|
||
| assertThat(validator.validate(validAdmin).isValid()).isTrue(); | ||
| assertThat(validator.validate(invalidAdmin).isValid()).isFalse(); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @MethodSource("provideValidators") | ||
| void testConstraintOnNonConditionClass(Validator<User> validator) { | ||
| User validUser = new User("Rawad", "rawad@gmail", 25); | ||
| User invalidUser = new User("Almog", "almog@gmail", 19); | ||
|
|
||
| assertThat(validator.validate(validUser).isValid()).isTrue(); | ||
|
||
| assertThat(validator.validate(invalidUser).isValid()).isFalse(); | ||
| } | ||
|
|
||
| static Stream<Arguments> provideValidators() { | ||
| ValidatorBuilder<User> userValidatorBuilder = ValidatorBuilder.of(User.class) | ||
| .constraint(User::getAge, "age", c -> c.greaterThan(20)); | ||
| Function<CharSequenceConstraint<Admin, String>, CharSequenceConstraint<Admin, String>> startsWithAdmin = ( | ||
| CharSequenceConstraint<Admin, String> c) -> c.startsWith("yavi"); | ||
|
|
||
| return Stream | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test case is difficult to understand. Please add the simple example in the pull request to the test case.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sure, no problem. |
||
| .of(Arguments.of(new ValidatorBuilder<>(userValidatorBuilder) | ||
| .constraintOnClass(Admin.class, | ||
| ValidatorBuilder.of(Admin.class) | ||
| .constraint(Admin::getGroup, "group", | ||
| startsWithAdmin) | ||
| .build()) | ||
| .build()), Arguments | ||
| .of(new ValidatorBuilder<>(userValidatorBuilder) | ||
| .constraintOnClass(Admin.class, | ||
| b -> b.constraint(Admin::getGroup, | ||
| "group", startsWithAdmin)) | ||
| .build())); | ||
| } | ||
|
|
||
| private static class Admin extends User { | ||
| private String group; | ||
|
|
||
| public Admin(String name, String email, int age, String group) { | ||
| super(name, email, age); | ||
| this.group = group; | ||
| } | ||
|
|
||
| public String getGroup() { | ||
| return group; | ||
| } | ||
| } | ||
| } | ||
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.
target name cannot be determined automatically.
Uh oh!
There was an error while loading. Please reload this page.
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'm a bit puzzled, actually, since I cant think of a target name that makes sense other than the class name.
that is because unlike regular .nest() usage, I am not validating a nested field, but rather the whole child class.
perhaps using nest() is some sort of an abuse here, given that I use it as work around to cast my entity, not access a nested field.
I'll see if I can come up with a different approach.
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.
ended up creating a new Validatable class dedicated to this case called InheritanceValidator, would love to hear your thoughts
Uh oh!
There was an error while loading. Please reload this page.
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 meant that the target name should be a parameter.
I don't think
InheritanceValidatoris needed.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.
Understood, I appreciate your review by the way. I find Yavi's design and philosophy thoroughly engaging.
I agree that the target name could have been a parameter, my concern arises from utilizing nest() for non-nested fields, and determining the appropriate target name for a child class.
what do you think?