spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.35k stars 38.03k forks source link

@Pattern error message "{0}" is not resolved. [SPR-17217] #21750

Open spring-projects-issues opened 6 years ago

spring-projects-issues commented 6 years ago

yoshikawaa opened SPR-17217 and commented

Application

Spring Boot Web MVC Application Spring Boot 2.0.4.RELEASE Sample : https://github.com/yoshikawaa/spring-boot-pattern-demo

Problem

There are cases where @Pattern error message {0} is not resolved.

javax.validation.constraints.Pattern.message = {0} must match "{regexp}".
spring.messages.basename = ValidationMessages
public class DemoForm {
    @Pattern(regexp = "\\d{3}")    // -> message [ valid must match "\d{3}".  ]
    private String valid;
    @Pattern(regexp = "\\d{1,3}") // -> message [ {0} must match "\d{1,3}".  ]
    private String invalid;
    // omit getter and setter
}

My validation is that if a range of digits is used in a regular expression, the mechanism for resolving {0} does not work properly.

Controller
    @PostMapping
    public String post(@Valid DemoForm form, BindingResult result) {
        result.getFieldErrors().forEach(e -> logger.info("field:{},error:{}", e.getField(), e.getDefaultMessage()));
        // log [ field:valid,error:{0} must match "\d{3}". ]
        // log [ field:invalid,error:{0} must match "\d{1,3}". ]
        return "demo";
    }

When logging BindingResult with Controller, you can see that LocalValidatorFactoryBean (Hibernate Validator) resolves message variable {regexp} beforehand. After that, when Spring MVC resolve the message, it is considered that the regular expression in the message is obstructing resolution of {0}.


Affects: 5.0.8

Reference URL: https://github.com/spring-projects/spring-boot/issues/14163

rstoyanchev commented 5 years ago

I've updated the description with the description from the Boot ticket.

jayanth1007 commented 5 years ago

This is Java issue which throws java.lang.IllegalArgumentException: unknown format type: 3. Spring core silently proceeding with the raw message in this case. org.springframework.context.support.MessageSourceSupport#formatMessage

MessageFormat messageFormat = new MessageFormat("{0} must match \"\\d{1,3}\"");
messageFormat.format(new Object[] {"valid"});
sbrannen commented 5 years ago

@jayanth1007, thanks for investigating this and providing your findings.

You're completely right: executing the following...

    public static void main(String[] args) {
        new MessageFormat("{0} must match \"\\d{1,3}\"");
    }

... results in:

Exception in thread "main" java.lang.IllegalArgumentException: unknown format type: 3
    at java.text.MessageFormat.makeFormat(MessageFormat.java:1526)
    at java.text.MessageFormat.applyPattern(MessageFormat.java:479)
    at java.text.MessageFormat.<init>(MessageFormat.java:362)

So, the MessageFormat constructor throws an exception immediately.

johnlinp commented 5 years ago

The above mentioned IllegalArgumentException is because of that MessageFormat tried to parse {1,3} as a FormatElement with the format of { ArgumentIndex , FormatType }, which is described in the java doc of MessageFormat.

Currently, the original message in @yoshikawaa's example is:

{0} must match "{regexp}".

it would be interpolated by a MessageInterpolator as:

{0} must match "\d{1,3}".

When MessageFormat sees it, it would start parsing {1,3} and then throw an exception.

I suggest that we should use single quotes to quote the "non-FormatElement" part of the message when interpolating the message, so that it won't be parsed by MessageFormat. For example, we can provide a custom MessageInterpolator which interpolate the message as:

{0} must match "'\d{1,3}'".

so that it can be successfully formatted by MessageFormat.format() as:

invalid must match "\d{1,3}".

One can test the above behavior by the code snippet:

public static void main(String[] args) {
    MessageFormat messageFormat = new MessageFormat("{0} must match \"'\\d{1,3}'\"");
    String formattedString = messageFormat.format(new Object[] {"invalid"});
    System.out.println(formattedString); // prints "invalid must match "\d{1,3}"."
}
johnlinp commented 5 years ago

Here is a demonstration of how to use a custom MessageInterpolator to fix the problem: https://github.com/yoshikawaa/spring-boot-pattern-demo/pull/1

However, I am not very sure about where to put the custom MessageInterpolator; should I put it in spring-context, spring-boot, or even hibernate-validator?

johnlinp commented 5 years ago

I guess this issue is related to https://github.com/spring-projects/spring-framework/issues/18167.

chenghaoharvey commented 4 years ago

Hi, may i know this problem was resolved or not? And another two issues was occurred:

MessageFormat.format("{0} is a number, {1} is a number, but i want to show single quote \"'\"", 1, 2) //print 1 is a number, 2 is a number, but i want to show single quote ""

Single quote was missing, what i want to display is:

1 is a number, 2 is a number, but i want to show single quote "'"

However, upper problems can be fixed by adding "'" before single quote as below:

MessageFormat.format("{0} is a number, {1} is a number, but i want to show single quote \"''\"", 1, 2) print 1 is a number, 2 is a number, but i want to show single quote "'" And PR is needed or not?