I am validating REST service request/bean in a spring-boot 2.3.1.RELEASE web application. Currently, I am using Hibernate Validator, though I am open to using any other way for validation.
Say, I have a model Foo, which I receive as a request in a Rest Controller. And I want to validate if completionDate is not null then status should be either "complete" or "closed".
@StatusValidate
public class Foo {
private String status;
private LocalDate completionDate;
// getters and setters
}
I created a custom class level annotation @StatusValidate.
@Constraint(validatedBy = StatusValidator.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StatusValidate {
String message() default "default status error";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
I created StatusValidator class.
public class StatusValidator implements ConstraintValidator<StatusValidate, Foo> {
@Override
public void initialize(StatusValidateconstraintAnnotation) {
}
@Override
public boolean isValid(Foovalue, ConstraintValidatorContext context) {
if (null != value.getCompletionDate() && (!value.getStatus().equalsIgnoreCase("complete") && !value.getStatus().equalsIgnoreCase("closed"))) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).
.addPropertyNode("status").addConstraintViolation();
return false;
}
return true;
}
}
When I validate Foo object (by using @Valid or @Validated or manually calling the validator.validate() method), I get following data in the ConstraintViolation.
Code:
// Update.class is a group
Set<ConstraintViolation<Foo>> constraintViolations = validator.validate(foo, Update.class);
constraintViolations.forEach(constraintViolation -> {
ErrorMessage errorMessage = new ErrorMessage();
errorMessage.setKey(constraintViolation.getPropertyPath().toString());
errorMessage.setValue(constraintViolation.getInvalidValue());
// Do something with errorMessage here
});
constraintViolation.getPropertyPath().toString() => status
constraintViolation.getInvalidValue() => (Foo object)
How can I set an invalid value (actual value of status attribute) in custom ConstraintValidator or anywhere else so that constraintViolation.getInvalidValue() returns value of status attribute?
OR
Is there a better way of validating request payload/bean where validation of an attribute depends on another attribute's value?
Edit : I can do something like
if(constraintViolation.getPropertyPath().toString().equals("status")) {
errorMessage.setValue(foo.getStatus());
}
but this would involve maintaining the String constant of attribute names somewhere for eg. "status". Though, in the StatusValidator also, I am setting the attribute name .addPropertyNode("status") which also I would like to avoid.
Summary : I am looking for a solution (not necessarily using custom validations or hibernate validator) where
- I can validate json requestor or a bean, for an attribute whose validations depends on values of other attributes.
- I don't have to maintain bean attribute names as String constants anywhere (maintenance nightmare).
- I am able to get the invalid property name and value both.