Skip to content
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

Provide localized field name in JSR-303 validation messages [SPR-6407] #11073

Closed
spring-projects-issues opened this issue Nov 22, 2009 · 2 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

Sebastian Beigel opened SPR-6407 and commented

I think it would be a good idea to provide the (localized) field name in JSR-303 validation messages as the first argument ("{0}"). It would be consistent with Spring's other validation (type conversion) messages and essential if you want to show a validation error summary (vs. showing the error message next to the input field in the UI, i.e. "Foo must match [a-z]+").

I have extended LocalValidatorFactoryBean and overridden validate(Object target, Errors errors) to include the field name (as a DefaultMessageSourceResolvable) as the first argument. But I think it should be included directly in LocalValidatorFactoryBean (i.e. SpringValidatorAdapter).

The code could look something like this (sorry, too lazy to make a patch :)

public void validate(Object target, Errors errors) {
    Set<ConstraintViolation<Object>> result = getValidator().validate(target);
    for (ConstraintViolation<Object> violation : result) {
        String field = violation.getPropertyPath().toString();
        FieldError fieldError = errors.getFieldError(field);
        if (fieldError == null || !fieldError.isBindingFailure()) {
            // build argument list...
            Collection<Object> args = new ArrayList<Object>();
            // ...containing the localized field name as first arg (trying the codes "model.field" and "field")...
            args.add(new DefaultMessageSourceResolvable(new String[] { errors.getObjectName() + "." + field, field }));
            // ...and JSR-303 validator's other arguments
            args.addAll(violation.getConstraintDescriptor().getAttributes().values());
            errors.rejectValue(field,
                    violation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(),
                    args.toArray(),
                    violation.getMessage());
        }
    }

Resolving to messages like this:

Pattern={0} must match {4}!
model.field=Foo
field=Bar

=> Foo must match [a-z]+

Of course you could provide all the messages in the "direct" form: "Pattern.field=Field must match {4}" (w/out the patch) -- but this is cumbersome and redundant :)


Affects: 3.0 RC2

Referenced from: commits 69124f9

@spring-projects-issues
Copy link
Collaborator Author

Pavla Nováková commented

Hi Sebastian,

good idea, I'm using little bit different approach to implement required behaviour, it may be helpful:

Logic implemented in my custom BeanValidator - see below (that integrates JSR-303 Validator and Spring MVC Validator) may be moved framework and the second part of this solution is to use custom format for JSR-303 ValidationMessages.properties : property value is using delimiter "-" for message key to spring message source and its arguments.

Example format of ValidationMessages.properties:

javax.validation.constraints.AssertTrue.message=constraint.assertTrue
javax.validation.constraints.DecimalMax.message=constraint.decimalMax-{value}
javax.validation.constraints.Size.message=constraint.size-{min},{max}

Then in my simple Spring message source - messages.properties

1. general messages
constraint.assertTrue=Must be true.
constraint.decimalMax=Must be lower of equal to {0}.
constraint.size=Size must be between {0} and {1}.
1. or using bean and field specific message
constraint.size.user.name=Your name length must be between {0} and {1}

Finally the implementation of my BeanValidator is:

@Component
public class BeanValidator {

    /** delimiters used in validator messages to split arguments */
    public static final String DEFAULT_ARG_DELIMITER = ",";
    public static final String MESSAGE_AND_ARGUMENTS_DELIMITER = "-";


    @Autowired private Validator validator;

    public boolean supports(Class clazz) {
        return true;
    }

    public boolean validate(Object target, Errors errors) {

        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(target);
        writeConstraintViolationsToErrors(constraintViolations, errors);
        return (constraintViolations.isEmpty());
        
    }

    public boolean validate(Object target, Errors errors, Class... groups) {

        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(target, groups);
        writeConstraintViolationsToErrors(constraintViolations, errors);
        return (constraintViolations.isEmpty());

    }


    private void writeConstraintViolationsToErrors(Set<ConstraintViolation<Object>> constraintViolations, Errors errors) {

        for (ConstraintViolation<Object> constraintViolation : constraintViolations) {

            String propertyPath = constraintViolation.getPropertyPath().toString();
            
            String rawMessage = constraintViolation.getMessage();
            Object[] args = null;
            String resolvedMessage = null;

            String[] msgAndArgs = StringUtils.split(rawMessage,MESSAGE_AND_ARGUMENTS_DELIMITER);
            Assert.isTrue(msgAndArgs.length < 3, "Badly formed validor message. Single '-' or no delimiter in message is required. Message: " + rawMessage);

            if (msgAndArgs.length == 1) resolvedMessage = rawMessage;
            else {
                resolvedMessage = msgAndArgs[0];
                args = StringUtils.split(msgAndArgs[1], DEFAULT_ARG_DELIMITER);
            }
            
            if (propertyPath != null) {
                errors.rejectValue(propertyPath, resolvedMessage, args, resolvedMessage);
                continue;
            }

            errors.reject(resolvedMessage, args, resolvedMessage);
         }

    }

    
    

This way I can use flexible Spring MVC error message resolution integrated with JSR-303 Validation.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Sebastian, I've implemented your suggestion for Spring 3.0 RC3 now since it really is very consistent with the established processing of bind errors in Spring.

Pavla, there are certainly alternative approaches here: We'll revisit this topic for Spring 3.1, based on the overall feedback that we'll be getting in the meantime.

Juergen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants