Skip to content

Commit

Permalink
Allow to select first value extractor type argument by default (#424)
Browse files Browse the repository at this point in the history
* Allow to select first value extractor type argument by default

* fix(deps): update dependency io.micronaut.kotlin:micronaut-kotlin-bom to v4.4.0

* fix(deps): update dependency io.micronaut.reactor:micronaut-reactor-bom to v3.5.0

* fix(deps): update dependency io.micronaut.rxjava2:micronaut-rxjava2-bom to v2.5.0	gradle/libs.versions.toml

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
  • Loading branch information
dstepanov and renovate[bot] authored Oct 14, 2024
1 parent 78a6e49 commit ebd8a00
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 23 deletions.
10 changes: 5 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ arquillian-container = "1.9.1.Final"
javax-annotation-api = "1.3.2"
jakarta-validation-tck = "3.0.1"

micronaut = "4.6.6"
micronaut-platform = "4.4.3"
micronaut = "4.7.0"
micronaut-platform = "4.6.3"
micronaut-docs = "2.0.0"
micronaut-test = "4.5.0"
micronaut-reactor = "3.4.1"
micronaut-rxjava2 = "2.4.0"
micronaut-kotlin = "4.3.0"
micronaut-reactor = "3.5.0"
micronaut-rxjava2 = "2.5.0"
micronaut-kotlin = "4.4.0"
micronaut-logging = "1.3.0"

# Gradle plugins
Expand Down
7 changes: 7 additions & 0 deletions tests/jakarta-validation-tck/tck-tests.xml
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,13 @@
<exclude name="testUnexpectedType"/>
</methods>
</class>

<!-- Select first value extractor type argument by default -->
<class name="org.hibernate.beanvalidation.tck.tests.valueextraction.definition.InvalidValueExtractorTest">
<methods>
<exclude name="noExtractedValueThrowsException"/>
</methods>
</class>
</classes>

</test>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.BeanDefinition;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import jakarta.validation.valueextraction.ValueExtractor;
Expand Down Expand Up @@ -78,10 +79,23 @@ protected DefaultValueExtractors(@Nullable BeanContext beanContext) {
final Collection<BeanRegistration<ValueExtractor>> valueExtractors = beanContext.getBeanRegistrations(ValueExtractor.class);
if (CollectionUtils.isNotEmpty(valueExtractors)) {
for (BeanRegistration<ValueExtractor> reg : valueExtractors) {
addValueExtractor(localValueExtractors, new ValueExtractorDefinition(
reg.getBeanDefinition().asArgument(),
reg.getBean()
));
BeanDefinition<ValueExtractor> beanDefinition = reg.getBeanDefinition();
Argument<ValueExtractor> argument = beanDefinition.asArgument();
if (argument.getType().equals(ValueExtractor.class)) {
addValueExtractor(localValueExtractors, new ValueExtractorDefinition(
argument,
reg.getBean()
));
} else {
List<Argument<?>> typeArguments = beanDefinition.getTypeArguments(ValueExtractor.class);
if (typeArguments.isEmpty()) {
throw new IllegalStateException("No value-extractors found for bean definition: " + beanDefinition);
}
addValueExtractor(localValueExtractors, new ValueExtractorDefinition(
Argument.of(ValueExtractor.class, beanDefinition.getAnnotationMetadata(), typeArguments.toArray(new Argument[0])),
reg.getBean()
));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ private static Integer findExtractedTypeArgumentIndex(@NotNull Argument<?> argum
if (typeArgumentIndex != null) {
return typeArgumentIndex;
}
if (typeParameters.length == 1) {
// On missing @ExtractedValue select first type parameter by default
return 0;
}
throw new ValueExtractorDefinitionException("ValueExtractor definition is missing @ExtractedValue on an argument: " + argument);
}

Expand All @@ -96,6 +100,10 @@ private static AnnotationValue<?> findExtractedValue(@NotNull Argument<?> argume
}
AnnotationValue<ExtractedValue> annotationValue = argument.getAnnotationMetadata().getAnnotation(ExtractedValue.class);
if (annotationValue == null) {
if (typeParameters.length == 1) {
// On missing @ExtractedValue select first type parameter by default
return AnnotationValue.builder(ExtractedValue.class).build();
}
throw new ValueExtractorDefinitionException("ValueExtractor definition '" + valueExtractor + "' is missing @ExtractedValue!");
}
if (annotationValue.classValue("type").isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
package io.micronaut.validation.validator.constraints

import io.micronaut.annotation.processing.TypeElementVisitorProcessor

import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
import io.micronaut.context.ApplicationContext
import io.micronaut.inject.beans.visitor.IntrospectedTypeElementVisitor
import io.micronaut.inject.visitor.TypeElementVisitor
import io.micronaut.validation.validator.Validator
import io.micronaut.validation.visitor.IntrospectedValidationIndexesVisitor
import io.micronaut.validation.visitor.ValidationVisitor
import spock.lang.AutoCleanup
import spock.lang.Shared

import javax.annotation.processing.SupportedAnnotationTypes

class ConstraintUnwrapSpec extends AbstractTypeElementSpec {

@Shared
Expand Down Expand Up @@ -259,11 +253,4 @@ class Test {
constraintViolations.iterator().next().message == "must not be null"
}

@SupportedAnnotationTypes("*")
static class MyTypeElementVisitorProcessor extends TypeElementVisitorProcessor {
@Override
protected Collection<TypeElementVisitor> findTypeElementVisitors() {
return [new ValidationVisitor(), new IntrospectedValidationIndexesVisitor(), new IntrospectedTypeElementVisitor()]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.micronaut.validation.validator.constraints.unwrapped;

public record MyOptional<V>(V value) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.micronaut.validation.validator.constraints.unwrapped;

import jakarta.inject.Singleton;
import jakarta.validation.valueextraction.UnwrapByDefault;
import jakarta.validation.valueextraction.ValueExtractor;

@UnwrapByDefault
@Singleton
public class MyOptionalExtractor implements ValueExtractor<MyOptional<?>> {

@Override
public void extractValues(MyOptional<?> originalValue, ValueReceiver receiver) {
receiver.value("value", originalValue.value());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.micronaut.validation.validator.constraints.unwrapped


import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
import io.micronaut.context.ApplicationContext
import io.micronaut.validation.validator.Validator
import spock.lang.AutoCleanup
import spock.lang.Shared

class ValueExtractorsSpec extends AbstractTypeElementSpec {

@Shared
@AutoCleanup
ApplicationContext context = ApplicationContext.run()

@Shared
Validator validator = context.getBean(Validator)

void "test @NotNull should be applied on the optional value"() {
given:
def introspection = buildBeanIntrospection('test.Test', """
package test;
import jakarta.validation.Payload;
import jakarta.validation.constraints.NotNull;
import io.micronaut.validation.validator.constraints.unwrapped.MyOptional;
@io.micronaut.core.annotation.Introspected
class Test {
@NotNull
private MyOptional<String> field;
public MyOptional<String> getField() {
return field;
}
public void setField(MyOptional<String> f) {
this.field = f;
}
}
""")
def instance = introspection.instantiate()
def prop = introspection.getProperty("field").get()
prop.set(instance, new MyOptional(null))
def constraintViolations = validator.validate(introspection, instance)

expect:
constraintViolations.size() == 1
constraintViolations.iterator().next().message == "must not be null"
}
}

0 comments on commit ebd8a00

Please sign in to comment.