Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
73 changes: 50 additions & 23 deletions src/main/java/am/ik/yavi/builder/ValidatorBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = ".";

Expand Down Expand Up @@ -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();
Copy link
Owner

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.

Copy link
Contributor Author

@ArielBerkovich ArielBerkovich Apr 1, 2024

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.

Copy link
Contributor Author

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

Copy link
Owner

@making making Apr 2, 2024

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 InheritanceValidator is needed.

Copy link
Contributor Author

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?


return constraintOnCondition(getClassConstraintCondition(clazz), TValidator);
}

/**
* @since 0.14.0
*/
Copy link
Owner

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
*/
Expand Down
71 changes: 71 additions & 0 deletions src/test/java/am/ik/yavi/core/ConstraintOnClassTest.java
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();
Copy link
Owner

@making making Apr 1, 2024

Choose a reason for hiding this comment

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

It is not enough to simply verify the results of validation. Please also verify the contents of the constraints (name and messageKey).

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
Copy link
Owner

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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;
}
}
}