Support binding to class with enum generic type
Closes gh-349
This commit is contained in:
@@ -178,8 +178,8 @@ public class GraphQlArgumentBinder {
|
||||
value = bindMap(name, (Map<String, Object>) rawValue, targetType, targetClass, bindingResult);
|
||||
}
|
||||
else {
|
||||
value = (targetClass.isAssignableFrom(rawValue.getClass()) ?
|
||||
rawValue : convertValue(name, rawValue, targetClass, bindingResult));
|
||||
value = (!targetClass.isAssignableFrom(rawValue.getClass()) ?
|
||||
convertValue(name, rawValue, targetType, targetClass, bindingResult) : rawValue);
|
||||
}
|
||||
|
||||
if (isOptional) {
|
||||
@@ -229,8 +229,8 @@ public class GraphQlArgumentBinder {
|
||||
Constructor<?> constructor = BeanUtils.getResolvableConstructor(targetClass);
|
||||
|
||||
Object value = constructor.getParameterCount() > 0 ?
|
||||
bindMapToObjectViaConstructor(rawMap, constructor, bindingResult) :
|
||||
bindMapToObjectViaSetters(rawMap, constructor, bindingResult);
|
||||
bindMapToObjectViaConstructor(rawMap, constructor, targetType, bindingResult) :
|
||||
bindMapToObjectViaSetters(rawMap, constructor, targetType, bindingResult);
|
||||
|
||||
bindingResult.popNestedPath();
|
||||
|
||||
@@ -261,7 +261,8 @@ public class GraphQlArgumentBinder {
|
||||
|
||||
@Nullable
|
||||
private Object bindMapToObjectViaConstructor(
|
||||
Map<String, Object> rawMap, Constructor<?> constructor, ArgumentsBindingResult bindingResult) {
|
||||
Map<String, Object> rawMap, Constructor<?> constructor, ResolvableType parentType,
|
||||
ArgumentsBindingResult bindingResult) {
|
||||
|
||||
String[] paramNames = BeanUtils.getParameterNames(constructor);
|
||||
Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||
@@ -270,8 +271,11 @@ public class GraphQlArgumentBinder {
|
||||
for (int i = 0; i < paramNames.length; i++) {
|
||||
String name = paramNames[i];
|
||||
boolean isOmitted = !rawMap.containsKey(name);
|
||||
ResolvableType paramType = ResolvableType.forConstructorParameter(constructor, i);
|
||||
args[i] = bindRawValue(name, rawMap.get(name), isOmitted, paramType, paramTypes[i], bindingResult);
|
||||
|
||||
ResolvableType targetType = ResolvableType.forType(
|
||||
ResolvableType.forConstructorParameter(constructor, i).getType(), parentType);
|
||||
|
||||
args[i] = bindRawValue(name, rawMap.get(name), isOmitted, targetType, paramTypes[i], bindingResult);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -287,20 +291,26 @@ public class GraphQlArgumentBinder {
|
||||
}
|
||||
|
||||
private Object bindMapToObjectViaSetters(
|
||||
Map<String, Object> rawMap, Constructor<?> constructor, ArgumentsBindingResult bindingResult) {
|
||||
Map<String, Object> rawMap, Constructor<?> constructor, ResolvableType parentType,
|
||||
ArgumentsBindingResult bindingResult) {
|
||||
|
||||
Object target = BeanUtils.instantiateClass(constructor);
|
||||
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(target);
|
||||
|
||||
for (Map.Entry<String, Object> entry : rawMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
TypeDescriptor type = beanWrapper.getPropertyTypeDescriptor(key);
|
||||
if (type == null) {
|
||||
TypeDescriptor typeDescriptor = beanWrapper.getPropertyTypeDescriptor(key);
|
||||
if (typeDescriptor == null) {
|
||||
// Ignore unknown property
|
||||
continue;
|
||||
}
|
||||
|
||||
ResolvableType targetType =
|
||||
ResolvableType.forType(typeDescriptor.getResolvableType().getType(), parentType);
|
||||
|
||||
Object value = bindRawValue(
|
||||
key, entry.getValue(), false, type.getResolvableType(), type.getType(), bindingResult);
|
||||
key, entry.getValue(), false, targetType, typeDescriptor.getType(), bindingResult);
|
||||
|
||||
try {
|
||||
if (value != null) {
|
||||
beanWrapper.setPropertyValue(key, value);
|
||||
@@ -320,18 +330,24 @@ public class GraphQlArgumentBinder {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private <T> T convertValue(
|
||||
String name, @Nullable Object rawValue, Class<T> type, ArgumentsBindingResult bindingResult) {
|
||||
String name, @Nullable Object rawValue, ResolvableType type, Class<T> clazz,
|
||||
ArgumentsBindingResult bindingResult) {
|
||||
|
||||
Object value = null;
|
||||
try {
|
||||
TypeConverter converter = (this.typeConverter != null ? this.typeConverter : new SimpleTypeConverter());
|
||||
value = converter.convertIfNecessary(rawValue, (Class<?>) type);
|
||||
TypeConverter converter =
|
||||
(this.typeConverter != null ? this.typeConverter : new SimpleTypeConverter());
|
||||
|
||||
value = converter.convertIfNecessary(
|
||||
rawValue, (Class<?>) clazz,
|
||||
(type.getSource() instanceof MethodParameter param ? new TypeDescriptor(param) : null));
|
||||
}
|
||||
catch (TypeMismatchException ex) {
|
||||
bindingResult.pushNestedPath(name);
|
||||
bindingResult.rejectValue(rawValue, ex.getErrorCode(), "Failed to convert argument value");
|
||||
bindingResult.popNestedPath();
|
||||
}
|
||||
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.graphql.data;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -31,8 +33,11 @@ import graphql.schema.DataFetchingEnvironment;
|
||||
import graphql.schema.DataFetchingEnvironmentImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.graphql.Book;
|
||||
import org.springframework.graphql.data.method.annotation.Argument;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
@@ -51,7 +56,7 @@ class GraphQlArgumentBinderTests {
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private final GraphQlArgumentBinder binder = new GraphQlArgumentBinder();
|
||||
private final GraphQlArgumentBinder binder = new GraphQlArgumentBinder(new DefaultFormattingConversionService());
|
||||
|
||||
|
||||
@Test
|
||||
@@ -113,6 +118,24 @@ class GraphQlArgumentBinderTests {
|
||||
assertThat(((ItemListHolder) result).getItems()).hasSize(0);
|
||||
}
|
||||
|
||||
@Test // gh-349
|
||||
void dataBindingToBeanWithEnumGenericType() throws Exception {
|
||||
|
||||
Map<String, Object> argumentMap =
|
||||
Collections.singletonMap("filter", Collections.singletonMap("enums", Arrays.asList("ONE", "TWO")));
|
||||
|
||||
Method method = EnumController.class.getMethod("enums", EnumInput.class);
|
||||
ResolvableType targetType = ResolvableType.forMethodParameter(new MethodParameter(method, 0));
|
||||
|
||||
Object result = this.binder.bind(
|
||||
DataFetchingEnvironmentImpl.newDataFetchingEnvironment().arguments(argumentMap).build(),
|
||||
"filter", targetType);
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(EnumInput.class);
|
||||
EnumInput<FancyEnum> input = (EnumInput<FancyEnum>) result;
|
||||
assertThat(input.getEnums()).hasSize(2).containsExactly(FancyEnum.ONE, FancyEnum.TWO);
|
||||
}
|
||||
|
||||
@Test // gh-280
|
||||
void dataBindingBindingError() {
|
||||
assertThatThrownBy(
|
||||
@@ -329,6 +352,24 @@ class GraphQlArgumentBinderTests {
|
||||
Collections.singletonMap("name", "second"));
|
||||
}
|
||||
|
||||
@Test // gh-349
|
||||
void primaryConstructorWithEnumGenericType() throws Exception {
|
||||
|
||||
Map<String, Object> argumentMap =
|
||||
Collections.singletonMap("filter", Collections.singletonMap("enums", Arrays.asList("ONE", "TWO")));
|
||||
|
||||
Method method = EnumController.class.getMethod("enums", ConstructorEnumInput.class);
|
||||
ResolvableType targetType = ResolvableType.forMethodParameter(new MethodParameter(method, 0));
|
||||
|
||||
Object result = this.binder.bind(
|
||||
DataFetchingEnvironmentImpl.newDataFetchingEnvironment().arguments(argumentMap).build(),
|
||||
"filter", targetType);
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(ConstructorEnumInput.class);
|
||||
ConstructorEnumInput<FancyEnum> input = (ConstructorEnumInput<FancyEnum>) result;
|
||||
assertThat(input.enums()).hasSize(2).containsExactly(FancyEnum.ONE, FancyEnum.TWO);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private Object bind(String json, ResolvableType targetType) throws Exception {
|
||||
@@ -570,4 +611,39 @@ class GraphQlArgumentBinderTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class EnumController {
|
||||
|
||||
public List<FancyEnum> enums(@Argument EnumInput<FancyEnum> filter) {
|
||||
return filter.getEnums();
|
||||
}
|
||||
|
||||
public List<FancyEnum> enums(@Argument ConstructorEnumInput<FancyEnum> filter) {
|
||||
return filter.enums();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum FancyEnum {
|
||||
ONE, TWO, THREE
|
||||
}
|
||||
|
||||
|
||||
static class EnumInput<E extends Enum<E>> {
|
||||
|
||||
private List<E> enums;
|
||||
|
||||
public List<E> getEnums() {
|
||||
return enums;
|
||||
}
|
||||
|
||||
public void setEnums(List<E> enums) {
|
||||
this.enums = enums;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
record ConstructorEnumInput<E extends Enum<E>>(List<E> enums) {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user