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

Merge update from micronaut-core #18

Merged
merged 5 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions validation-processor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {

testImplementation mn.micronaut.http.client
testImplementation mn.micronaut.inject.java.test
testImplementation mn.micronaut.inject.groovy.test

if (!JavaVersion.current().isJava9Compatible()) {
testImplementation files(org.gradle.internal.jvm.Jvm.current().toolsJar)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.micronaut.validation.visitor

import io.micronaut.aop.Around
import io.micronaut.ast.transform.test.AbstractBeanDefinitionSpec
import io.micronaut.core.beans.BeanIntrospection
import io.micronaut.inject.ProxyBeanDefinition
import io.micronaut.inject.writer.BeanDefinitionVisitor
import io.micronaut.inject.writer.BeanDefinitionWriter
import io.micronaut.validation.ValidatedParseSpec

import java.time.LocalDate

class ValidatedParseSpecGroovy extends AbstractBeanDefinitionSpec {
void "test constraints on beans make them @Validated"() {
given:
def definition = buildBeanDefinition('validateparse1.Test','''
package validateparse1
import io.micronaut.context.annotation.Executable
import javax.validation.Valid
import javax.validation.constraints.NotBlank
@jakarta.inject.Singleton
class Test {
@Executable
void setName(@NotBlank String name) {}
@Executable
void setName2(@Valid String name) {}
}
''')

expect:
definition.findMethod("setName", String).get().hasStereotype(ValidatedParseSpec.VALIDATED_ANN)
definition.findMethod("setName2", String).get().hasStereotype(ValidatedParseSpec.VALIDATED_ANN)
}

void "test annotation default values on a groovy property"() {
given:
BeanIntrospection beanIntrospection = buildBeanIntrospection('validateparse2.Test','''
package validateparse2;
import io.micronaut.core.annotation.Introspected
import javax.validation.Constraint
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
@Introspected
class Test {
@ValidURLs
List<String> webs
}
@Constraint(validatedBy = [])
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface ValidURLs {
String message() default "invalid url"
}
''')

expect:
beanIntrospection.getProperty("webs").isPresent()
beanIntrospection.getRequiredProperty("webs", List).annotationMetadata.getDefaultValue("validateparse2.ValidURLs", "message", String).get() == "invalid url"
}

void "test constraints on a declarative client makes it @Validated"() {
given:
def definition = buildBeanDefinition('validateparse3.ExchangeRates' + BeanDefinitionVisitor.PROXY_SUFFIX,'''
package validateparse3
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import javax.validation.constraints.PastOrPresent
import java.time.LocalDate
@Client("https://exchangeratesapi.io")
interface ExchangeRates {
@Get("{date}")
String rate(@PastOrPresent LocalDate date)
}
''')

expect:
definition.findMethod("rate", LocalDate).get().hasStereotype(ValidatedParseSpec.VALIDATED_ANN)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.micronaut.aop.MethodInterceptor;
import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.validation.validator.ExecutableMethodValidator;
import io.micronaut.validation.validator.ReactiveValidator;
Expand Down Expand Up @@ -48,15 +49,19 @@ public class ValidatingInterceptor implements MethodInterceptor<Object, Object>

private final @Nullable ExecutableValidator executableValidator;
private final @Nullable ExecutableMethodValidator micronautValidator;
private final ConversionService conversionService;

/**
* Creates ValidatingInterceptor from the validatorFactory.
*
* @param micronautValidator The micronaut validator use if no factory is available
* @param validatorFactory Factory returning initialized {@code Validator} instances
* @param conversionService The conversion service
*/
public ValidatingInterceptor(@Nullable Validator micronautValidator,
@Nullable ValidatorFactory validatorFactory) {
@Nullable ValidatorFactory validatorFactory,
ConversionService conversionService) {
this.conversionService = conversionService;

if (validatorFactory != null) {
javax.validation.Validator validator = validatorFactory.getValidator();
Expand Down Expand Up @@ -112,7 +117,7 @@ public Object intercept(MethodInvocationContext<Object, Object> context) {
}
}
if (micronautValidator instanceof ReactiveValidator) {
InterceptedMethod interceptedMethod = InterceptedMethod.of(context);
InterceptedMethod interceptedMethod = InterceptedMethod.of(context, conversionService);
try {
switch (interceptedMethod.resultType()) {
case PUBLISHER:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.InjectionPoint;
import io.micronaut.inject.MethodReference;
import io.micronaut.inject.ProxyBeanDefinition;
import io.micronaut.inject.annotation.AnnotatedElementValidator;
import io.micronaut.inject.validation.BeanDefinitionValidator;
import io.micronaut.validation.validator.constraints.ConstraintValidator;
Expand Down Expand Up @@ -616,17 +617,20 @@ public <T> void validateBean(
@NonNull BeanDefinition<T> definition,
@NonNull T bean
) throws BeanInstantiationException {
final BeanIntrospection<T> introspection = (BeanIntrospection<T>) getBeanIntrospection(bean);
if (introspection != null) {
Class<T> beanType;
if (definition instanceof ProxyBeanDefinition<?> proxyBeanDefinition) {
beanType = (Class<T>) proxyBeanDefinition.getTargetType();
} else {
beanType = definition.getBeanType();
}
final BeanIntrospection<T> introspection = (BeanIntrospection<T>) getBeanIntrospection(bean, beanType); if (introspection != null) {
dstepanov marked this conversation as resolved.
Show resolved Hide resolved
Set<ConstraintViolation<T>> errors = validate(introspection, bean);
final Class<?> beanType = bean.getClass();
failOnError(resolutionContext, errors, beanType);
} else if (bean instanceof Intercepted && definition.hasStereotype(ConfigurationReader.class)) {
final Collection<ExecutableMethod<T, ?>> executableMethods = definition.getExecutableMethods();
if (CollectionUtils.isNotEmpty(executableMethods)) {
Set<ConstraintViolation<T>> violations = new HashSet<>();
final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext(bean);
final Class<T> beanType = definition.getBeanType();
final Class<?>[] interfaces = beanType.getInterfaces();
if (ArrayUtils.isNotEmpty(interfaces)) {
context.addConstructorNode(interfaces[0].getSimpleName());
Expand All @@ -651,6 +655,8 @@ public <T> void validateBean(

failOnError(resolutionContext, violations, beanType);
}
} else {
throw new BeanInstantiationException(resolutionContext, "Cannot validate bean [" + beanType.getName() + "]. No bean introspection present. Please add @Introspected.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import io.micronaut.context.ApplicationContext
import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.context.exceptions.BeanInstantiationException
import io.micronaut.core.annotation.Nullable
import io.micronaut.core.beans.BeanIntrospection
import io.micronaut.core.convert.ConversionService
import io.micronaut.core.order.OrderUtil
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
Expand Down Expand Up @@ -71,7 +71,7 @@ class ValidatedSpec extends Specification {
Object intercept(InvocationContext context) {
return null
}
}, new ValidatingInterceptor(null, null)]
}, new ValidatingInterceptor(null, null, ConversionService.SHARED)]
OrderUtil.sort(list)

expect:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package io.micronaut.validation.validator
import io.micronaut.context.ApplicationContext
import io.micronaut.context.annotation.Executable
import io.micronaut.context.annotation.Prototype
import io.micronaut.context.annotation.Value
import io.micronaut.context.exceptions.BeanInstantiationException
import io.micronaut.core.annotation.Introspected
import io.micronaut.core.reflect.ClassUtils
import io.micronaut.validation.validator.resolver.CompositeTraversableResolver
import jakarta.inject.Singleton
import spock.lang.AutoCleanup
Expand All @@ -21,7 +24,7 @@ class ValidatorSpec extends Specification {

@Shared
@AutoCleanup
ApplicationContext applicationContext = ApplicationContext.run()
ApplicationContext applicationContext = ApplicationContext.run(["a.number": 40])
@Shared
Validator validator = applicationContext.getBean(Validator)

Expand Down Expand Up @@ -792,7 +795,6 @@ class ValidatorSpec extends Specification {
violations[0].invalidValue == ""
}

@Ignore("https://github.com/micronaut-projects/micronaut-core/issues/8301")
void "test cascade to container with setter"() {
given:
def salad = new ValidatorSpecClasses.SaladWithSetter()
Expand All @@ -806,6 +808,90 @@ class ValidatorSpec extends Specification {
violations.size() == 1
violations[0].invalidValue == ""
}

void "test @Introspected is required to validate the bean"() {
when:
applicationContext.getBean(A)
then:
BeanInstantiationException e = thrown()
e.message.contains('''Cannot validate bean [io.micronaut.validation.validator.A]. No bean introspection present. Please add @Introspected.''')
and:
ClassUtils.forName('io.micronaut.validation.validator.$A$Definition', getClass().getClassLoader()).isPresent()
ClassUtils.forName('io.micronaut.validation.validator.$A$Definition$Intercepted', getClass().getClassLoader()).isEmpty()
}

void "test @Introspected is required to validate the bean and it's intercepted if one of the methods requires validation"() {
when:
def beanB = applicationContext.getBean(B)
then:
BeanInstantiationException e = thrown()
e.message.contains('''number - must be less than or equal to 20''')
and:
ClassUtils.forName('io.micronaut.validation.validator.$B$Definition', getClass().getClassLoader()).isPresent()
ClassUtils.forName('io.micronaut.validation.validator.$B$Definition$Intercepted', getClass().getClassLoader()).isPresent()
}

void "test @Introspected is required to validate the bean and it's intercepted if one of the methods requires validation 2"() {
when:
def beanC = applicationContext.getBean(C)
then:
beanC.number == 40
when:
beanC.updateNumber(100)
then:
Exception e = thrown()
e.message.contains('''updateNumber.number: must be less than or equal to 50''')
beanC.number == 40

/*
dstepanov marked this conversation as resolved.
Show resolved Hide resolved
when:
beanC.number = 100
then:
e = thrown()
e.message.contains('''updateNumber.number: must be less than or equal to 50''')
beanC.number == 40
*/

and:
ClassUtils.forName('io.micronaut.validation.validator.$C$Definition', getClass().getClassLoader()).isPresent()
ClassUtils.forName('io.micronaut.validation.validator.$C$Definition$Intercepted', getClass().getClassLoader()).isPresent()
}
}

@Singleton
class A {
@Max(20l)
@NotNull
@Value('${a.number}')
Integer number
}

@Introspected
@Singleton
class B {
@Max(20l)
@NotNull
@Value('${a.number}')
Integer number
void updateNumber(@Max(20l)
@NotNull
Integer number) {
this.number = number
}
}

@Introspected
@Singleton
class C {
@Max(50l)
@NotNull
@Value('${a.number}')
Integer number
void updateNumber(@Max(50l)
@NotNull
Integer number) {
this.number = number
}
}

@Introspected
Expand Down