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

Calling addConstraintViolation on a ConstraintValidatorContext results in a wrong validation message #397

Open
tbashour opened this issue Jul 30, 2024 · 5 comments

Comments

@tbashour
Copy link

tbashour commented Jul 30, 2024

Expected Behavior

message and messageTemplate are filled correctly

Actual Behaviour

message is filled with the custome template and message template is filled with the interpolated message.

Steps To Reproduce

The Problem is in the addConstraintViolation method in the DefaultConstraintViolationBuilder class.
The mehods adds a violation by calling:


new DefaultConstraintViolation(this.constraintValidatorContext.getRootBean(), this.constraintValidatorContext.getRootClass(), (Object)null, (Object)null, this.messageTemplate, this.messageInterpolator.interpolate(this.messageTemplate, new MessageInterpolator.Context() {
            public ConstraintDescriptor<?> getConstraintDescriptor() {
                return DefaultConstraintViolationBuilder.this.constraintValidatorContext.constraint;
            }

            public Object getValidatedValue() {
                return null;
            }

            public <T> T unwrap(Class<T> type) {
                throw new ValidationException("Not supported!");
            }
        })

It should be:

new DefaultConstraintViolation(this.constraintValidatorContext.getRootBean(), this.constraintValidatorContext.getRootClass(), (Object)null, (Object)null, this.messageInterpolator.interpolate(this.messageTemplate, new MessageInterpolator.Context() {
            public ConstraintDescriptor<?> getConstraintDescriptor() {
                return DefaultConstraintViolationBuilder.this.constraintValidatorContext.constraint;
            }

            public Object getValidatedValue() {
                return null;
            }

            public <T> T unwrap(Class<T> type) {
                throw new ValidationException("Not supported!");
            }
        }, this.messageTemplate)

Environment Information

No response

Example Application

package com.micronaut.test.constraints;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import jakarta.validation.constraints.Pattern;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ FIELD, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { CollectionPatternValidator.class })
public @interface CollectionPattern {

    String regexp();

    Pattern.Flag[] flags() default {};

    String message() default "{jakarta.validation.constraints.Pattern.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

package com.micronaut.test.constraints;

import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.validation.validator.constraints.ConstraintValidator;
import io.micronaut.validation.validator.constraints.ConstraintValidatorContext;
import jakarta.inject.Singleton;

import java.util.Collection;

@Singleton
public class CollectionPatternValidator implements ConstraintValidator<CollectionPattern, Collection<String>> {

    @Override
    public boolean isValid(@Nullable Collection<String> values,
                           @NonNull AnnotationValue<CollectionPattern> annotationMetadata,
                           @NonNull ConstraintValidatorContext context) {

        if (values == null || values.isEmpty()) {
            return true;
        }

        final String regexp = annotationMetadata.getRequiredValue("regexp", String.class);
        final java.util.regex.Pattern regex = java.util.regex.Pattern.compile(regexp);

        context.disableDefaultConstraintViolation();

        boolean valid = true;
        for (String value : values) {
            if (value != null && !regex.matcher(value).matches()) {

                context.buildConstraintViolationWithTemplate("must match  \"{regexp}\"")
                        .addPropertyNode("[" + value + "]")
                        .addConstraintViolation();

                valid = false;
            }
        }
        return valid;
    }
}

package com.micronaut.test.constraints;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import io.micronaut.validation.validator.Validator;
import jakarta.inject.Inject;
import jakarta.validation.ConstraintViolation;
import lombok.Getter;
import lombok.Setter;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.not;

@MicronautTest(startApplication = false)
public class CustomerConstraintTest {

    @Introspected
    @Getter
    @Setter
    public class ExampleBean {
        @CollectionPattern(regexp = "[a-z]")
        private List<String> list = new ArrayList<>();
    }

    @Inject
    Validator validator;


    @Test
    void should_return_interpolated_message()
    {
        // Given
        ExampleBean bean = new ExampleBean();
        bean.getList().add("11111");

        // When
        Set<ConstraintViolation<ExampleBean>> violations = validator.validate(bean);

        // Then
        assertThat("violations", violations, not(empty()));
        assertThat("violations[0].message", violations.iterator().next().getMessage(), containsString("[a-z]"));
        assertThat("violations[0].message", violations.iterator().next().getMessageTemplate(), containsString("regexp"));
    }

}

Version

4.6.1

@dstepanov
Copy link
Contributor

Please create a reproducer

@tbashour
Copy link
Author

Please create a reproducer

I have written a junit testcase which should reporduce the issue

@dstepanov
Copy link
Contributor

Can you just create a PR with a fix and a test?

@tbashour
Copy link
Author

Can you just create a PR with a fix and a test?

I really don't have time to do so ... not in the coming two weeks

Are you still missing some information? Or you also don't have time to fix the issue?

@dstepanov
Copy link
Contributor

It’s faster to create a PR directly, I will have time only next week.

@graemerocher graemerocher transferred this issue from micronaut-projects/micronaut-core Aug 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants