Skip to content

Commit

Permalink
Fixed: Validator doesn't throw UnexpectedTypeException in retry
Browse files Browse the repository at this point in the history
  • Loading branch information
altro3 committed Nov 12, 2024
1 parent a555d96 commit cf698f7
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 208 deletions.
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;
}

}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -126,45 +126,50 @@ 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()) {
ConstraintValidator<A, T> found = bean.get();
validatorCache.put(key, found);
return Optional.of(found);
}

validatorCache.put(key, ConstraintValidator.VALID);

} 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
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);
}
}

return Optional.empty();
}

Expand Down

0 comments on commit cf698f7

Please sign in to comment.