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

Extend BeanValidationContext to ExecutableValidator #320

Merged
merged 1 commit into from
Mar 4, 2024
Merged
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
Expand Up @@ -17,6 +17,7 @@

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.beans.BeanProperty;
import io.micronaut.core.util.ArrayUtils;
import java.util.Arrays;
import java.util.List;

Expand All @@ -43,9 +44,13 @@ default List<Class<?>> groups() {
* @return The context
*/
static @NonNull BeanValidationContext fromGroups(Class<?>... groups) {
return new DefaultBeanValidationContext(
groups != null ? Arrays.asList(groups) : List.of()
);
if (ArrayUtils.isEmpty(groups)) {
return DEFAULT;
} else {
return new DefaultBeanValidationContext(
Arrays.asList(groups)
);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,17 +377,23 @@ public <T> Set<ConstraintViolation<T>> validateParameters(@NonNull T object,
@NonNull ExecutableMethod method,
@NonNull Object[] parameterValues,
@NonNull Class<?>... groups) {
requireNonNull("groups", groups);
return validateParameters(object, method, parameterValues, BeanValidationContext.fromGroups(groups));
}

@Override
public <T> Set<ConstraintViolation<T>> validateParameters(T object, ExecutableMethod method, @NonNull Object[] parameterValues, BeanValidationContext validationContext) {
requireNonNull("parameterValues", parameterValues);
requireNonNull("object", object);
requireNonNull("method", method);
requireNonNull("groups", groups);
requireNonNull("context", validationContext);
final Argument<?>[] arguments = method.getArguments();
final int argLen = arguments.length;
if (argLen != parameterValues.length) {
throw new IllegalArgumentException("The method parameter array must have exactly " + argLen + " elements.");
}

DefaultConstraintValidatorContext<T> context = new DefaultConstraintValidatorContext<>(this, null, object, BeanValidationContext.fromGroups(groups));
DefaultConstraintValidatorContext<T> context = new DefaultConstraintValidatorContext<>(this, null, object, validationContext);
try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableParameterValues(parameterValues)) {
try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addMethodNode(method)) {
AnnotationMetadata methodAnnotationMetadata = method.getAnnotationMetadata().getDeclaredMetadata();
Expand Down Expand Up @@ -468,8 +474,18 @@ public <T> Set<ConstraintViolation<T>> validateReturnValue(@NonNull T object,
@NonNull Class<?>... groups) {
requireNonNull("groups", groups);

return validateReturnValue(
bean,
executableMethod,
returnValue,
BeanValidationContext.fromGroups(groups)
);
}

@Override
public <T> Set<ConstraintViolation<T>> validateReturnValue(T bean, ExecutableMethod<?, Object> executableMethod, Object returnValue, BeanValidationContext validationContext) {
final ReturnType<Object> returnType = executableMethod.getReturnType();
final DefaultConstraintValidatorContext<T> context = new DefaultConstraintValidatorContext<>(this, null, bean, BeanValidationContext.fromGroups(groups));
final DefaultConstraintValidatorContext<T> context = new DefaultConstraintValidatorContext<>(this, null, bean, validationContext);

try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableReturnValue(returnValue)) {
try (ValidationPath.ContextualPath ignored2 = context.getCurrentPath().addMethodNode(executableMethod)) {
Expand Down Expand Up @@ -549,12 +565,22 @@ public <T> Set<ConstraintViolation<T>> validateConstructorParameters(Class<? ext
@NonNull Class<?>[] groups) {
requireNonNull("groups", groups);

return validateConstructorParameters(
beanType,
constructorArguments,
parameterValues,
BeanValidationContext.fromGroups(groups)
);
}

@Override
public <T> Set<ConstraintViolation<T>> validateConstructorParameters(Class<? extends T> beanType, @NonNull Argument<?>[] constructorArguments, @NonNull Object[] parameterValues, BeanValidationContext validationContext) {
parameterValues = parameterValues != null ? parameterValues : ArrayUtils.EMPTY_OBJECT_ARRAY;
final int argLength = constructorArguments.length;
if (parameterValues.length != argLength) {
throw new IllegalArgumentException("Expected exactly [" + argLength + "] constructor arguments");
}
DefaultConstraintValidatorContext<T> context = (DefaultConstraintValidatorContext<T>) new DefaultConstraintValidatorContext<>(this, null, beanType, BeanValidationContext.fromGroups(groups));
DefaultConstraintValidatorContext<T> context = (DefaultConstraintValidatorContext<T>) new DefaultConstraintValidatorContext<>(this, null, beanType, validationContext);
try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableParameterValues(parameterValues)) {
try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addConstructorNode(beanType.getSimpleName(), constructorArguments)) {
validateParametersInternal(context, null, AnnotationMetadata.EMPTY_METADATA, parameterValues, constructorArguments, argLength);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ public interface ExecutableMethodValidator extends ExecutableValidator {
@NonNull Object[] parameterValues,
@Nullable Class<?>... groups);

/**
* Validate the parameter values of the given {@link ExecutableMethod}.
* @param object The object
* @param method The method
* @param parameterValues The values
* @param context The context
* @param <T> The object type
* @return The constraint violations.
*/
@NonNull <T> Set<ConstraintViolation<T>> validateParameters(
@NonNull T object,
@NonNull ExecutableMethod method,
@NonNull Object[] parameterValues,
@Nullable BeanValidationContext context);

/**
* Validate the parameter values of the given {@link ExecutableMethod}.
* @param object The object
Expand Down Expand Up @@ -96,6 +111,22 @@ public interface ExecutableMethodValidator extends ExecutableValidator {
@Nullable Object returnValue,
@Nullable Class<?>... groups);


/**
* Validates the return value of a {@link ExecutableMethod}.
* @param object The object
* @param executableMethod The method
* @param returnValue The return value
* @param validationContext The validation context
* @param <T> The object type
* @return A set of contstraint violations
*/
@NonNull <T> Set<ConstraintViolation<T>> validateReturnValue(
@NonNull T object,
@NonNull ExecutableMethod<?, Object> executableMethod,
@Nullable Object returnValue,
@Nullable BeanValidationContext validationContext);

/**
* Validates parameters for the given introspection and values.
* @param introspection The introspection
Expand Down Expand Up @@ -126,6 +157,22 @@ <T> Set<ConstraintViolation<T>> validateConstructorParameters(
@Nullable Class<?>[] groups
);

/**
* Validates arguments for the given bean type and constructor arguments.
* @param beanType The bean type
* @param constructorArguments The constructor arguments
* @param parameterValues The parameter values
* @param validationContext The validation context
* @param <T> The generic type of the bean
* @return A set of constraint violations, if any
*/
<T> Set<ConstraintViolation<T>> validateConstructorParameters(
@NonNull Class<? extends T> beanType,
@NonNull Argument<?>[] constructorArguments,
@NonNull Object[] parameterValues,
@Nullable BeanValidationContext validationContext
);

@Override
@NonNull <T> Set<ConstraintViolation<T>> validateParameters(@NonNull T object, @NonNull Method method, @NonNull Object[] parameterValues, @Nullable Class<?>... groups);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.micronaut.validation.validator.replacement

import io.micronaut.aop.InterceptorBean
import io.micronaut.aop.MethodInvocationContext
import io.micronaut.context.annotation.Replaces
import io.micronaut.core.beans.BeanProperty
import io.micronaut.core.convert.ConversionService
import io.micronaut.test.annotation.MockBean
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import io.micronaut.validation.Pojo
import io.micronaut.validation.Validated
import io.micronaut.validation.ValidatingInterceptor
import io.micronaut.validation.validator.BeanValidationContext
import io.micronaut.validation.validator.Validator
import jakarta.inject.Inject
import jakarta.inject.Singleton
import jakarta.validation.ConstraintViolation
import jakarta.validation.ConstraintViolationException
import jakarta.validation.Valid
import jakarta.validation.ValidatorFactory
import spock.lang.Specification

@MicronautTest
class ReplaceInterceptorSpec extends Specification {

@Inject TestService testService

void "test replacement interceptor"() {
when:
testService.validatePojo(new Pojo(email: "junk", name: ""))

then:
def e = thrown(ConstraintViolationException)
e.message == 'validatePojo.pojo.email: Email should be valid'
}

@MockBean(ValidatingInterceptor)
@InterceptorBean(Validated)
static class MyInterceptor extends ValidatingInterceptor {
Validator micronautValidator
MyInterceptor(Validator micronautValidator, ValidatorFactory validatorFactory, ConversionService conversionService) {
super(micronautValidator, validatorFactory, conversionService)
this.micronautValidator = micronautValidator;
}

@Override
Object intercept(MethodInvocationContext<Object, Object> context) {
if (context.parameterValues[0] instanceof Pojo) {
def constraintViolations = micronautValidator.forExecutables()
.validateParameters(
context.parameterValues[0],
context.executableMethod,
context.parameterValues,
new BeanValidationContext() {
boolean isPropertyValidated(Object object, BeanProperty<Object, Object> property) {
return property.name == 'email'
}
}
)
if (constraintViolations) {
throw new ConstraintViolationException(constraintViolations)
}
return context.proceed()
} else {
return super.intercept(context)
}
}
}

@Singleton
@Validated
static class TestService {
void validatePojo(@Valid Pojo pojo) {
println(pojo)
}
}
}
Loading