Add support for Bean Validation
This commit adds support for validating handler method inputs after binding from the data fetching environment and before invoking the handler method. This support is based on Bean Validation and is enabled only if the application context contains a `Validator` bean. Closes gh-110
This commit is contained in:
@@ -76,7 +76,9 @@ configure(moduleProjects) {
|
||||
dependency "com.jayway.jsonpath:json-path:2.5.0"
|
||||
dependency "org.skyscreamer:jsonassert:1.5.0"
|
||||
dependency "com.h2database:h2:1.4.200"
|
||||
dependency "javax.validation:validation-api:2.0.1.Final"
|
||||
dependency "org.hibernate:hibernate-core:5.6.1.Final"
|
||||
dependency "org.hibernate.validator:hibernate-validator:6.2.0.Final"
|
||||
dependencySet(group: 'org.mongodb', version: '4.3.2') {
|
||||
entry 'mongodb-driver-sync'
|
||||
entry 'mongodb-driver-reactivestreams'
|
||||
|
||||
@@ -34,6 +34,7 @@ dependencies {
|
||||
compileOnly 'org.springframework:spring-webmvc'
|
||||
compileOnly 'org.springframework:spring-websocket'
|
||||
compileOnly 'javax.servlet:javax.servlet-api'
|
||||
compileOnly 'javax.validation:validation-api'
|
||||
compileOnly 'javax.websocket:javax.websocket-api'
|
||||
|
||||
compileOnly 'io.micrometer:micrometer-core'
|
||||
@@ -62,7 +63,10 @@ dependencies {
|
||||
testImplementation 'io.projectreactor:reactor-test'
|
||||
testImplementation 'io.projectreactor.netty:reactor-netty'
|
||||
testImplementation 'javax.servlet:javax.servlet-api'
|
||||
testImplementation 'javax.validation:validation-api'
|
||||
testImplementation 'org.hibernate.validator:hibernate-validator'
|
||||
testImplementation 'org.apache.tomcat.embed:tomcat-embed-core'
|
||||
testImplementation 'org.apache.tomcat.embed:tomcat-embed-el'
|
||||
testImplementation 'org.apache.tomcat.embed:tomcat-embed-websocket'
|
||||
testImplementation 'org.springframework.boot:spring-boot-actuator-autoconfigure'
|
||||
testImplementation 'io.micrometer:micrometer-core'
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.graphql.GraphQlService;
|
||||
import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer;
|
||||
import org.springframework.graphql.execution.BatchLoaderRegistry;
|
||||
import org.springframework.graphql.execution.GraphQlSource;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -50,6 +51,16 @@ class GraphQlServiceAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureValidation() {
|
||||
this.contextRunner.withUserConfiguration(ValidationConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(AnnotatedControllerConfigurer.class);
|
||||
assertThat(context.getBean(AnnotatedControllerConfigurer.class))
|
||||
.extracting("validator").isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class GraphQlSourceConfiguration {
|
||||
|
||||
@@ -59,4 +70,13 @@ class GraphQlServiceAutoConfigurationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ValidationConfiguration {
|
||||
|
||||
@Bean
|
||||
public LocalValidatorFactoryBean defaultValidator() {
|
||||
return new LocalValidatorFactoryBean();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -821,6 +821,52 @@ You can use `@Argument` on a `Map<String, Object>` argument, to obtain all argum
|
||||
values. The name attribute on `@Argument` must not be set.
|
||||
|
||||
|
||||
[[controllers-schema-mapping-validation]]
|
||||
==== `@Argument` validation
|
||||
|
||||
If a {spring-framework-ref-docs}/core.html#validation-beanvalidation-overview[Bean Validation]
|
||||
`Validator` (or typically, a `LocalValidatorFactoryBean`) bean is present in the application context,
|
||||
the `AnnotatedControllerConfigurer` will auto-detect it and configure support for validation.
|
||||
Controller arguments annotated with `@Valid` and `@Validated` are then validated before method invocation.
|
||||
|
||||
Bean Validation lets you declare constraints on types, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class BookInput {
|
||||
|
||||
@NotNull
|
||||
private String title;
|
||||
|
||||
@NotNull
|
||||
@Size(max=13)
|
||||
private String isbn;
|
||||
}
|
||||
----
|
||||
|
||||
We can then mark our argument for validation with `@Valid`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Controller
|
||||
public class BookController {
|
||||
|
||||
@MutationMapping
|
||||
public Book addBook(@Argument @Valid BookInput bookInput) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
If an error occurs during validation, a `ConstraintViolationException` is thrown and can be
|
||||
later <<execution-exceptions,resolved with a custom `DataFetcherExceptionResolver`>>.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Unlike Spring MVC, handler method signatures do not support the injection of `BindingResult`
|
||||
for reacting to validation errors: those are globally dealt with as exceptions.
|
||||
====
|
||||
|
||||
[[controllers-schema-mapping-projectedpayload-argument]]
|
||||
==== `@ProjectPayload` Interface
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ dependencies {
|
||||
compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-core'
|
||||
compileOnly 'org.jetbrains.kotlin:kotlin-stdlib'
|
||||
|
||||
compileOnly 'javax.validation:validation-api'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||
testImplementation 'org.assertj:assertj-core'
|
||||
testImplementation 'org.mockito:mockito-core'
|
||||
@@ -35,6 +37,7 @@ dependencies {
|
||||
testImplementation 'org.springframework.data:spring-data-jpa'
|
||||
testImplementation 'com.h2database:h2'
|
||||
testImplementation 'org.hibernate:hibernate-core'
|
||||
testImplementation 'org.hibernate.validator:hibernate-validator'
|
||||
testImplementation 'org.springframework.data:spring-data-mongodb'
|
||||
testImplementation 'org.mongodb:mongodb-driver-sync'
|
||||
testImplementation 'org.mongodb:mongodb-driver-reactivestreams'
|
||||
@@ -44,8 +47,10 @@ dependencies {
|
||||
testImplementation 'com.querydsl:querydsl-core'
|
||||
testImplementation 'com.querydsl:querydsl-collections'
|
||||
testImplementation 'javax.servlet:javax.servlet-api'
|
||||
testImplementation 'javax.validation:validation-api'
|
||||
testImplementation 'com.jayway.jsonpath:json-path'
|
||||
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
testImplementation 'org.apache.tomcat.embed:tomcat-embed-el:9.0.55'
|
||||
|
||||
testRuntimeOnly 'org.apache.logging.log4j:log4j-core'
|
||||
testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl'
|
||||
|
||||
@@ -27,6 +27,8 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.Validator;
|
||||
|
||||
import graphql.schema.DataFetcher;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import graphql.schema.FieldCoordinates;
|
||||
@@ -66,6 +68,7 @@ import org.springframework.util.StringUtils;
|
||||
* registers them as {@link DataFetcher}s.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class AnnotatedControllerConfigurer
|
||||
@@ -93,6 +96,9 @@ public class AnnotatedControllerConfigurer
|
||||
"org.springframework.security.core.context.SecurityContext",
|
||||
AnnotatedControllerConfigurer.class.getClassLoader());
|
||||
|
||||
private final static boolean beanValidationPresent = ClassUtils.isPresent(
|
||||
"javax.validation.executable.ExecutableValidator",
|
||||
AnnotatedControllerConfigurer.class.getClassLoader());
|
||||
|
||||
@Nullable
|
||||
private ApplicationContext applicationContext;
|
||||
@@ -100,6 +106,9 @@ public class AnnotatedControllerConfigurer
|
||||
@Nullable
|
||||
private HandlerMethodArgumentResolverComposite argumentResolvers;
|
||||
|
||||
@Nullable
|
||||
private HandlerMethodInputValidator validator;
|
||||
|
||||
@Nullable
|
||||
private ConversionService conversionService;
|
||||
|
||||
@@ -109,6 +118,9 @@ public class AnnotatedControllerConfigurer
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@link ConversionService} used for binding handler arguments.
|
||||
*/
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
@@ -145,6 +157,10 @@ public class AnnotatedControllerConfigurer
|
||||
|
||||
// This works as a fallback, after other resolvers
|
||||
this.argumentResolvers.addResolver(new SourceMethodArgumentResolver());
|
||||
|
||||
if (beanValidationPresent) {
|
||||
this.validator = HandlerMethodInputValidatorFactory.create(obtainApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,7 +170,7 @@ public class AnnotatedControllerConfigurer
|
||||
findHandlerMethods().forEach((info) -> {
|
||||
DataFetcher<?> dataFetcher;
|
||||
if (!info.isBatchMapping()) {
|
||||
dataFetcher = new SchemaMappingDataFetcher(info, this.argumentResolvers);
|
||||
dataFetcher = new SchemaMappingDataFetcher(info, this.argumentResolvers, this.validator);
|
||||
}
|
||||
else {
|
||||
String dataLoaderKey = registerBatchLoader(info);
|
||||
@@ -281,7 +297,7 @@ public class AnnotatedControllerConfigurer
|
||||
}
|
||||
|
||||
Assert.hasText(typeName,
|
||||
"No parentType specified, and a source/parent method argument was also not found: " +
|
||||
"No parentType specified, and a source/parent method argument was also not found: " +
|
||||
handlerMethod.getShortLogMessage());
|
||||
|
||||
return new MappingInfo(typeName, field, batchMapping, handlerMethod);
|
||||
@@ -304,7 +320,7 @@ public class AnnotatedControllerConfigurer
|
||||
String methodParameters = Arrays.stream(method.getGenericParameterTypes())
|
||||
.map(Type::getTypeName)
|
||||
.collect(Collectors.joining(",", "(", ")"));
|
||||
return mappingInfo.getCoordinates() + " => " + method.getName() + methodParameters;
|
||||
return mappingInfo.getCoordinates() + " => " + method.getName() + methodParameters;
|
||||
})
|
||||
.collect(Collectors.joining("\n\t", "\n\t" + formattedType + ":" + "\n\t", ""));
|
||||
}
|
||||
@@ -378,11 +394,16 @@ public class AnnotatedControllerConfigurer
|
||||
|
||||
private final HandlerMethodArgumentResolverComposite argumentResolvers;
|
||||
|
||||
@Nullable
|
||||
private final HandlerMethodInputValidator validator;
|
||||
|
||||
private final boolean subscription;
|
||||
|
||||
public SchemaMappingDataFetcher(MappingInfo info, HandlerMethodArgumentResolverComposite resolvers) {
|
||||
public SchemaMappingDataFetcher(MappingInfo info, HandlerMethodArgumentResolverComposite resolvers,
|
||||
@Nullable HandlerMethodInputValidator validator) {
|
||||
this.info = info;
|
||||
this.argumentResolvers = resolvers;
|
||||
this.validator = validator;
|
||||
this.subscription = this.info.getCoordinates().getTypeName().equalsIgnoreCase("Subscription");
|
||||
}
|
||||
|
||||
@@ -404,7 +425,7 @@ public class AnnotatedControllerConfigurer
|
||||
@Override
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public Object get(DataFetchingEnvironment environment) throws Exception {
|
||||
return new DataFetcherHandlerMethod(getHandlerMethod(), this.argumentResolvers, this.subscription).invoke(environment);
|
||||
return new DataFetcherHandlerMethod(getHandlerMethod(), this.argumentResolvers, this.validator, this.subscription).invoke(environment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,11 +441,23 @@ public class AnnotatedControllerConfigurer
|
||||
@Override
|
||||
public Object get(DataFetchingEnvironment env) {
|
||||
DataLoader<?, ?> dataLoader = env.getDataLoaderRegistry().getDataLoader(this.dataLoaderKey);
|
||||
if (dataLoader == null) {
|
||||
if (dataLoader == null) {
|
||||
throw new IllegalStateException("No DataLoader for key '" + this.dataLoaderKey + "'");
|
||||
}
|
||||
return dataLoader.load(env.getSource());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a Validator bean in the context and configure validation support
|
||||
*/
|
||||
static class HandlerMethodInputValidatorFactory {
|
||||
|
||||
@Nullable
|
||||
static HandlerMethodInputValidator create(ApplicationContext context) {
|
||||
Validator validator = context.getBeanProvider(Validator.class).getIfAvailable();
|
||||
return validator != null ? new HandlerMethodInputValidator(validator) : null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,17 +48,29 @@ public class DataFetcherHandlerMethod extends InvocableHandlerMethodSupport {
|
||||
|
||||
private final HandlerMethodArgumentResolverComposite resolvers;
|
||||
|
||||
@Nullable
|
||||
private final HandlerMethodInputValidator validator;
|
||||
|
||||
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
private final boolean subscription;
|
||||
|
||||
|
||||
public DataFetcherHandlerMethod(
|
||||
HandlerMethod handlerMethod, HandlerMethodArgumentResolverComposite resolvers, boolean subscription) {
|
||||
/**
|
||||
* Constructor with a parent handler method.
|
||||
* @param handlerMethod the handler method
|
||||
* @param resolvers the argument resolvers
|
||||
* @param validator the input validator
|
||||
* @param subscription whether the field being fetched is of subscription type
|
||||
*/
|
||||
public DataFetcherHandlerMethod(HandlerMethod handlerMethod,
|
||||
HandlerMethodArgumentResolverComposite resolvers, @Nullable HandlerMethodInputValidator validator,
|
||||
boolean subscription) {
|
||||
|
||||
super(handlerMethod);
|
||||
Assert.isTrue(!resolvers.getResolvers().isEmpty(), "No argument resolvers");
|
||||
this.resolvers = resolvers;
|
||||
this.validator = validator;
|
||||
this.subscription = subscription;
|
||||
}
|
||||
|
||||
@@ -70,6 +82,13 @@ public class DataFetcherHandlerMethod extends InvocableHandlerMethodSupport {
|
||||
return this.resolvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured input validator.
|
||||
*/
|
||||
@Nullable
|
||||
public HandlerMethodInputValidator getValidator() {
|
||||
return this.validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the method after resolving its argument values in the context of
|
||||
@@ -93,6 +112,9 @@ public class DataFetcherHandlerMethod extends InvocableHandlerMethodSupport {
|
||||
Object[] args;
|
||||
try {
|
||||
args = getMethodArgumentValues(environment);
|
||||
if (this.validator != null) {
|
||||
this.validator.validate(this, args);
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
return Mono.error(ex);
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.graphql.data.method.HandlerMethod;
|
||||
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
|
||||
|
||||
/**
|
||||
* Strategy for validating a {@link HandlerMethod} input before invocation, based on JSR-303.
|
||||
* This is called after all {@link HandlerMethodArgumentResolver} have been involved.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class HandlerMethodInputValidator {
|
||||
|
||||
private final Validator validator;
|
||||
|
||||
/**
|
||||
* Create the input validator backed by a JSR-303 Validator instance.
|
||||
*/
|
||||
public HandlerMethodInputValidator(Validator validator) {
|
||||
Assert.notNull(validator, "validator should not be null");
|
||||
if (validator instanceof LocalValidatorFactoryBean) {
|
||||
this.validator = ((LocalValidatorFactoryBean) validator).getValidator();
|
||||
}
|
||||
else if (validator instanceof SpringValidatorAdapter) {
|
||||
this.validator = validator.unwrap(Validator.class);
|
||||
}
|
||||
else {
|
||||
this.validator = validator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the input validator backed by a default
|
||||
* {@link Validation#buildDefaultValidatorFactory() factory instance}.
|
||||
*/
|
||||
public HandlerMethodInputValidator() {
|
||||
this(Validation.buildDefaultValidatorFactory().getValidator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the {@link HandlerMethod} input before invocation, throwing
|
||||
* an {@link ConstraintViolationException} if validation fails.
|
||||
*
|
||||
* @param handlerMethod the handler method for the current query
|
||||
* @param arguments the resolved arguments for the method invocation
|
||||
*/
|
||||
public void validate(HandlerMethod handlerMethod, Object[] arguments) {
|
||||
Class<?>[] validationGroups = determineValidationGroups(handlerMethod);
|
||||
Set<ConstraintViolation<Object>> result = this.validator.forExecutables()
|
||||
.validateParameters(handlerMethod.getBean(), handlerMethod.getMethod(), arguments, validationGroups);
|
||||
if (!result.isEmpty()) {
|
||||
throw new ConstraintViolationException(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the validation groups to validate against for the given handler method.
|
||||
* <p>Default are the validation groups as specified in the {@link Validated} annotation
|
||||
* on the containing target class of the method.
|
||||
* @param method the current HandlerMethod
|
||||
* @return the applicable validation groups as a Class array
|
||||
*/
|
||||
private Class<?>[] determineValidationGroups(HandlerMethod method) {
|
||||
Validated validatedAnn = AnnotationUtils.findAnnotation(method.getMethod(), Validated.class);
|
||||
if (validatedAnn == null) {
|
||||
validatedAnn = AnnotationUtils.findAnnotation(method.getBeanType(), Validated.class);
|
||||
}
|
||||
return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.data.method.annotation.support;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.assertj.core.api.IterableAssert;
|
||||
import org.assertj.core.api.ThrowableAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.graphql.data.method.HandlerMethod;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link HandlerMethodInputValidator}
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class HandlerMethodInputValidatorTests {
|
||||
|
||||
private final HandlerMethodInputValidator validator = new HandlerMethodInputValidator();
|
||||
|
||||
@Test
|
||||
void shouldFailWithNullValidator() {
|
||||
assertThatThrownBy(() -> new HandlerMethodInputValidator(null)).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreMethodsWithoutAnnotations() throws Exception {
|
||||
HandlerMethod method = findHandlerMethod(MyValidBean.class, "notValidatedMethod");
|
||||
assertThatNoException().isThrownBy(() -> validator.validate(method, new Object[] {"test", 12}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRaiseValidationErrorForAnnotatedParams() throws Exception {
|
||||
HandlerMethod method = findHandlerMethod(MyValidBean.class, "myValidMethod");
|
||||
assertViolations(() -> validator.validate(method, new Object[] {null, 2}))
|
||||
.anyMatch(violation -> violation.getPropertyPath().toString().equals("myValidMethod.arg0"));
|
||||
assertViolations(() -> validator.validate(method, new Object[] {"test", 12}))
|
||||
.anyMatch(violation -> violation.getPropertyPath().toString().equals("myValidMethod.arg1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRaiseValidationErrorForAnnotatedParamsWithGroups() throws Exception {
|
||||
HandlerMethod myValidMethodWithGroup = findHandlerMethod(MyValidBeanWithGroup.class, "myValidMethodWithGroup");
|
||||
assertViolations(() -> validator.validate(myValidMethodWithGroup, new Object[] {null}))
|
||||
.anyMatch(violation -> violation.getPropertyPath().toString().equals("myValidMethodWithGroup.arg0"));
|
||||
HandlerMethod myValidMethodWithGroupOnType = findHandlerMethod(MyValidBeanWithGroup.class, "myValidMethodWithGroupOnType");
|
||||
assertViolations(() -> validator.validate(myValidMethodWithGroupOnType, new Object[] {null}))
|
||||
.anyMatch(violation -> violation.getPropertyPath().toString().equals("myValidMethodWithGroupOnType.arg0"));
|
||||
}
|
||||
|
||||
|
||||
private HandlerMethod findHandlerMethod(Class<?> handlerType, String methodName) {
|
||||
Object handler = BeanUtils.instantiateClass(handlerType);
|
||||
Method method = Arrays.stream(handlerType.getMethods())
|
||||
.filter(m -> m.getName().equals(methodName))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Invalid method name"));
|
||||
return new HandlerMethod(handler, method);
|
||||
}
|
||||
|
||||
private IterableAssert<ConstraintViolation> assertViolations(ThrowableAssert.ThrowingCallable callable) {
|
||||
return assertThatThrownBy(callable)
|
||||
.isInstanceOf(ConstraintViolationException.class)
|
||||
.extracting("constraintViolations")
|
||||
.asInstanceOf(InstanceOfAssertFactories.iterable(ConstraintViolation.class));
|
||||
}
|
||||
|
||||
public static class MyValidBean {
|
||||
|
||||
public String notValidatedMethod(String arg0, int arg1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
public Object myValidMethod(@NotNull String arg0, @Max(10) int arg1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface FirstGroup {
|
||||
}
|
||||
|
||||
|
||||
public interface SecondGroup {
|
||||
}
|
||||
|
||||
@Validated(FirstGroup.class)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface GroupOnParam {
|
||||
}
|
||||
|
||||
@Validated(SecondGroup.class)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface GroupOnType {
|
||||
}
|
||||
|
||||
@GroupOnType
|
||||
public static class MyValidBeanWithGroup {
|
||||
|
||||
@GroupOnParam
|
||||
public Object myValidMethodWithGroup(@NotNull(groups = {FirstGroup.class}) String arg0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object myValidMethodWithGroupOnType(@NotNull(groups = {SecondGroup.class}) String arg0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user