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

Fixed: Validator doesn't throw UnexpectedTypeException in retry #448

Open
wants to merge 1 commit into
base: 4.9.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.micronaut.docs.validation.retry;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.docs.validation.retry.validation.ValidInternalId;
import io.micronaut.docs.validation.retry.validation.ValidatedByFactory;

@Introspected
public record ValidatedBean(
@ValidatedByFactory
String value,
@ValidInternalId
int internalId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.micronaut.docs.validation.retry;

import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import jakarta.validation.UnexpectedTypeException;
import jakarta.validation.Validator;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

@MicronautTest(rebuildContext = true, startApplication = false)
public class ValidatorTest {

@Inject
Validator validator;

@Test
void BUG_REPRODUCTION_CASE_validateValidBean_shouldNotFail() {
var bean = new ValidatedBean("foobar", 42);

assertThrows(UnexpectedTypeException.class, () -> validator.validate(bean));
assertThrows(UnexpectedTypeException.class, () -> validator.validate(bean));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.micronaut.docs.validation.retry.validation;

import jakarta.validation.Constraint;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

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

@Documented
@Constraint(validatedBy = {})
@Target({
ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@NotNull
@Positive
@Max(Integer.MAX_VALUE)
public @interface ValidInternalId {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.micronaut.docs.validation.retry.validation;

import jakarta.validation.Constraint;
import jakarta.validation.constraints.Size;

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

@Documented
@Constraint(validatedBy = {})
@Target({
ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Size(max = 32)
public @interface ValidatedByFactory {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.micronaut.docs.validation.retry.validation;

import io.micronaut.context.annotation.Factory;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.validation.validator.constraints.ConstraintValidator;
import jakarta.inject.Singleton;

@Factory
@Introspected
public class ValidatorFactory {

@Singleton
ConstraintValidator<ValidatedByFactory, CharSequence> validatedByFactoryValidator() {
return (ignored1, ignored2, ignored3) -> true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1442,7 +1442,7 @@ public boolean isValid(E value, AnnotationValue<Annotation> annotationMetadata,
}
validator = constraintValidatorRegistry.findConstraintValidator(constraintType, elementArgument.getType()).orElse(null);
}
if (validator == null) {
if (validator == null || validator == ConstraintValidator.VALID) {
throw new UnexpectedTypeException("Cannot find a constraint validator for constraint: " + constraintType.getName() + " and type: " + elementArgument.getType());
}
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,45 +126,48 @@ public DefaultConstraintValidators(@Nullable BeanContext beanContext) {
public <A extends Annotation, T> Optional<ConstraintValidator<A, T>> findConstraintValidator(@NonNull Class<A> constraintType, @NonNull Class<T> targetType) {
ArgumentUtils.requireNonNull("constraintType", constraintType);
ArgumentUtils.requireNonNull("targetType", targetType);
final ValidatorKey key = new ValidatorKey(constraintType, targetType);
final var key = new ValidatorKey(constraintType, targetType);
targetType = (Class<T>) ReflectionUtils.getWrapperType(targetType);

ConstraintValidator<?, ?> constraintValidator = internalValidators.get(key);
if (constraintValidator != null) {
return Optional.of((ConstraintValidator<A, T>) constraintValidator);
}

constraintValidator = validatorCache.get(key);
if (constraintValidator != null) {
return Optional.of((ConstraintValidator<A, T>) constraintValidator);
}

Optional<ConstraintValidator<A, T>> local = findInternalConstraintValidator(constraintType, targetType);
if (local.isPresent()) {
validatorCache.put(key, local.get());
return local;
}

if (beanContext != null) {
Argument<ConstraintValidator<A, T>> argument = (Argument) Argument.of(ConstraintValidator.class);
final Qualifier<ConstraintValidator<A, T>> qualifier = Qualifiers.byTypeArguments(
constraintType,
targetType
);
Optional<ConstraintValidator<A, T>> bean = beanContext.findBean(argument, qualifier);
if (bean.isPresent()) {
validatorCache.put(key, bean.get());
return bean;
}

} else {
constraintValidator = validatorCache.get(key);
if (constraintValidator != null) {
return Optional.of((ConstraintValidator<A, T>) constraintValidator);
} else {
Optional<ConstraintValidator<A, T>> local = findInternalConstraintValidator(constraintType, targetType);
if (local.isPresent()) {
validatorCache.put(key, local.get());
return local;
} else if (beanContext != null) {
Argument<ConstraintValidator<A, T>> argument = (Argument) Argument.of(ConstraintValidator.class);
final Qualifier<ConstraintValidator<A, T>> qualifier = Qualifiers.byTypeArguments(
constraintType,
targetType
);
Optional<ConstraintValidator<A, T>> bean = beanContext.findBean(argument, qualifier);
if (bean.isEmpty()) {
validatorCache.put(key, ConstraintValidator.VALID);
} else {
ConstraintValidator<A, T> found = bean.get();
validatorCache.put(key, found);
return Optional.of(found);
}
} else {
// last chance lookup
final ConstraintValidator<A, T> cv = findLocalConstraintValidator(constraintType, targetType)
.orElse((ConstraintValidator<A, T>) ConstraintValidator.VALID);
validatorCache.put(key, cv);
if (cv != ConstraintValidator.VALID) {
return Optional.of(cv);
}
}
// last chance lookup
Optional<ConstraintValidator<A, T>> cv = findLocalConstraintValidator(constraintType, targetType);
if (cv.isPresent()) {
validatorCache.put(key, cv.get());
return cv;
}
}

validatorCache.put(key, ConstraintValidator.VALID);

return Optional.empty();
}

Expand Down
Loading