Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building Dynamic ConstraintViolation Error Messages

I've written a validation annotation implemented by a custom ConstraintValidator. I also want to generate very specific ConstraintViolation objects that use values computed during the validation process during message interpolation.

public class CustomValidator 
  implements ConstraintValidator<CustomAnnotation, ValidatedType> {

...
  @Override
  public boolean isValid(ValidatedType value, ConstraintValidatorContext context) {
    // Figure out that the value is not valid.
    // Now, I want to add a violation whose error message requires arguments.
  }
}

A hypothetical error message in my message source:

CustomAnnotation.notValid = The supplied value {value} was not valid because {reason}.

The context passed into the isValid method provides an interface for building up a constraint violation, and finally adding it to the context. However, I can't seem to figure out how to use it. According to this documention for the version I'm using, I can add bean and property nodes to the violation. These are the only additional details I can specify to the violation definition, but I don't understand how they might map to parameters in the error message.

Million dollar question: how can I pass dynamic parameters to my validation error messages using a custom validator? I would like to fill in those {value} and {reason} fields using the ConstraintValidatorContext's interface for building violations.

Obtaining an instance of the message source and interpolating the message within the custom validator is not an option - the messages coming out of validation get interpolated no matter what, and interpolating internally will result in some messages being interpolated twice, potentially annihilating escaped single quotes or other characters with special meanings in my message definitions file.

like image 207
Misha Avatar asked May 16 '14 19:05

Misha


2 Answers

That's not possible with the standardized Bean Valiation API, but there is a way in Hibernate Validator, the BV reference implementation.

You need to unwrap the ConstraintValidatorContext into a HibernateConstraintValidatorContext which gives you access to the addExpressionVariable() method:

public class MyFutureValidator implements ConstraintValidator<Future, Date> {

    public void initialize(Future constraintAnnotation) {}

    public boolean isValid(Date value, ConstraintValidatorContext context) {
        Date now = GregorianCalendar.getInstance().getTime();

        if ( value.before( now ) ) {
            HibernateConstraintValidatorContext hibernateContext =
                context.unwrap( HibernateConstraintValidatorContext.class );

            hibernateContext.disableDefaultConstraintViolation();
            hibernateContext.addExpressionVariable( "now", now )
                .buildConstraintViolationWithTemplate( "Must be after ${now}" )
                .addConstraintViolation();

            return false;
        }

        return true;
    }
}

The reference guide has some more details.

like image 62
Gunnar Avatar answered Oct 15 '22 09:10

Gunnar


If you use message codes you can simply add something like {0} in them.

For example: "The field {0} must not be empty."

And then use hibernateContext.addMessageParameter("0", fieldName); instead of addExpressionVariable(...).

That worked for me.

like image 27
YuKa Avatar answered Oct 15 '22 07:10

YuKa