-
Notifications
You must be signed in to change notification settings - Fork 62
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
Avoid the need to repeat property as String
on ValidatorBuilder
#269
Comments
@odrotbohm Is MethodInvocationRecorder using reflection? |
It would indirectly, as of course, the dummy invocation on the proxy will trigger the interceptors reflectively. I saw the annotation processor but would like to be able not having to annotate the DTO types with validation-specific annotations as I use Yavi to avoid annotations in the first place. As omitting the explicit |
There are already so many As far as I know, users seem to accept repeating the "field name" string. There was a pull request to omit it once. Also, the more powerful method of building a Validator by combining Single Value Validators (I prefer this method) does not use method references and therefore does not benefit from the omission by reflection. |
As a workaround you can use this: .constraint(stringByPropertyName("lastname"), …) public static <T> StringConstraintMeta<T> stringByPropertyName(String name) {
return new ReflectiveStringPropertyResolver<>(name);
} public static final class ReflectiveStringPropertyResolver<T> implements StringConstraintMeta<T> {
private final String name;
private ReflectiveStringPropertyResolver(String name) {
this.name = name;
}
@Override
public String name() {
return name;
}
@Override
public Function<T, String> toValue() {
return object -> {
try {
// the line below uses 'commons-beanutils:commons-beanutils:1.9.4'
return (String) PropertyUtils.getProperty(object, name);
} catch (Exception e) {
return null;
}
};
}
} |
Thank you for @making this awesome library. I came to this library because I don't like reflection backed validation. And so far this library hasn't failed me yet. Please do not make this into another |
I'd also like to thank @making for this excellent library. I came to Yavi because of the functional approach, fluent API, and high flexibility. Most importantly, it allows me to integrate the validation process with reactive/non-blocking programming (e.g., Spring WebFlux). IMHO, the issue is not only about repeating the property name but also about safety. Writing the property as a string is error-prompt and hard to catch during refactors. I support Yavi's philosophy of not using reflection, but I think that's most important during the validation process. If a bit of reflection is used for the violation message only and makes the API safer and easier to use, it's more than justifiable to go for it! Regarding annotation processing, I'd also prefer to avoid adding more annotations to my models, especially if I moved to Yavi to not put validation-related annotations on their fields. I prefer those to be as clean as possible and to have annotations only related to the model itself (nullability, relationships, etc.) Typos are more frequent than we think, so to avoid the string name, I had to create a wrapper around Click to see the SerializedLambda approachFirst, create a
public interface MethodReferenceReflection extends Serializable {
default String fieldName() {
final var serialized = Maybe.just("writeReplace")
.resolve(getClass()::getDeclaredMethod)
.doOnSuccess(method -> method.setAccessible(true))
.resolve(method -> method.invoke(this))
.cast(SerializedLambda.class)
.orThrow(ReferenceReflectionException::new);
final var methods = Maybe.just(serialized)
.map(SerializedLambda::getImplClass)
.map(x -> x.replace("/", "."))
.resolve(Class::forName)
.map(Class::getDeclaredMethods)
.orThrow(ReferenceReflectionException::new);
return stream(methods)
.map(Method::getName)
.filter(isEqual(serialized.getImplMethodName()))
.findFirst()
.orElseThrow(ReferenceReflectionException::new);
}
class ReferenceReflectionException extends RuntimeException {
private static final String MESSAGE = "Unexpected error processing method referece";
public ReferenceReflectionException(final Throwable cause) {
super(MESSAGE, cause);
}
public ReferenceReflectionException() {
super(MESSAGE);
}
}
} Now, you can extend functional interfaces with public interface ToBoolean<T> extends Function<T, Boolean>, MethodReferenceReflection {
} You can use public ValidatorBuilder<T> constraint(
ToBoolean<T> f,
Function<BooleanConstraint<T>,
BooleanConstraint<T>> c
) {
return this.constraint(f, f.fieldName(), c, BooleanConstraint::new);
} Anyways, congrats again on this awesome library! I hope this feedback is useful to all! 🙂 |
@JoseLion Thanks for the comment. |
I appreciate the intent to keep Yavi as reflection free as possible. And I would totally not have submitted a suggestion that introduces reflection in the actual validation steps. What is suggested here is to add a bit of code to the validator initialization phase that's totally opt-in. It could be entirely disregarded by users that err on the “completely reflection free” side of the camp but avoids having to use String references during property identification. Those usually become a maintenance burden over the lifetime of an application and easily break during refactorings. Adding the additional overloads would allow users to responsibly choose between the “all into performance” approach or the “reduced maintenance overhead” depending on their project requirements and choice. |
@odrotbohm Just wondering how are you generating your validators. In my case I am generating them from jsonschemas using java poet. So whenever the schemas are changed everything is updated. |
I think they call it “coding”, a procedure that involves pressing fingers on keys of a keyboard. 😉. |
My next big goal is to make YAVI webassembly ready. At the moment the webassembly + Java ecosystem is immature and quite constrained. Including reflection in YAVI code makes it super hard to compile code containing YAVI to wasm. So this project itself does not use the reflection api. However, it is still open to use the reflection api in another project using a pluggable mechanism in YAVI. |
@odrotbohm @making Lets keep this project reflection-less. |
Currently, the API to build constraints allows to define the property to validate for via method references, but requires the property name to be defined as String additionally.
The property name could actually be defaulted by immediately applying the function provided as first parameter on a proxy of the type to define the constraints for (
Person
in this case) and registering a method interceptor on that to capture the name of the invoked method. An example of this can be found in Spring Data'sMethodInvocationRecorder
(example of exposure here).I think it's fine to reject all problematic derivations, potentially caused by anything but a simple method reference, at runtime and recommend users to name the property explicitly. However, as most validations are likely to use accessor methods, it would avoid quite a bit of boilerplate to not have to repeat the property name as
String
.The text was updated successfully, but these errors were encountered: