Support contructor binding for input arguments

See gh-139
This commit is contained in:
Koen Punt
2021-09-19 20:25:43 +02:00
committed by Brian Clozel
parent f69aa39211
commit 2f5aa588ef
4 changed files with 64 additions and 7 deletions

View File

@@ -1,3 +1,6 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.5.31'
}
description = "GraphQL Support for Spring Applications" description = "GraphQL Support for Spring Applications"
dependencies { dependencies {
@@ -34,6 +37,7 @@ dependencies {
testRuntimeOnly 'org.apache.logging.log4j:log4j-core' testRuntimeOnly 'org.apache.logging.log4j:log4j-core'
testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl' testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
} }
test { test {
@@ -42,3 +46,16 @@ test {
events "passed", "skipped", "failed" events "passed", "skipped", "failed"
} }
} }
repositories {
mavenCentral()
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.graphql.data.method.annotation.support;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Stack; import java.util.Stack;
@@ -107,13 +108,32 @@ public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentReso
Object target; Object target;
if (rawValue instanceof Map) { if (rawValue instanceof Map) {
Constructor<?> ctor = BeanUtils.getResolvableConstructor(targetType); Constructor<?> ctor = BeanUtils.getResolvableConstructor(targetType);
target = BeanUtils.instantiateClass(ctor); MutablePropertyValues propertyValues = extractPropertyValues((Map) rawValue);
DataBinder dataBinder = new DataBinder(target);
Assert.isTrue(ctor.getParameterCount() == 0, if (ctor.getParameterCount() == 0) {
() -> "Argument of type [" + targetType.getName() + target = BeanUtils.instantiateClass(ctor);
"] cannot be instantiated because of missing default constructor."); DataBinder dataBinder = new DataBinder(target);
MutablePropertyValues mpvs = extractPropertyValues((Map) rawValue); dataBinder.bind(propertyValues);
dataBinder.bind(mpvs); } else {
// Data class constructor
DataBinder binder = new DataBinder(null);
String[] paramNames = BeanUtils.getParameterNames(ctor);
Class<?>[] paramTypes = ctor.getParameterTypes();
Object[] args = new Object[paramTypes.length];
for (int i = 0; i < paramNames.length; i++) {
String paramName = paramNames[i];
Object value = propertyValues.get(paramName);
value = (value instanceof List ? ((List<?>) value).toArray() : value);
MethodParameter methodParam = new MethodParameter(ctor, i);
if (value == null && methodParam.isOptional()) {
args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
}
else {
args[i] = binder.convertIfNecessary(value, paramTypes[i], methodParam);
}
}
target = BeanUtils.instantiateClass(ctor, args);
}
} }
else if (targetType.isAssignableFrom(rawValue.getClass())) { else if (targetType.isAssignableFrom(rawValue.getClass())) {
return returnValue(rawValue, targetType); return returnValue(rawValue, targetType);

View File

@@ -86,6 +86,18 @@ class ArgumentMethodArgumentResolverTests {
.hasFieldOrPropertyWithValue("authorId", 42L); .hasFieldOrPropertyWithValue("authorId", 42L);
} }
@Test
void shouldResolveKotlinBeanArgument() throws Exception {
Method addBook = ClassUtils.getMethod(BookController.class, "ktAddBook", KotlinBookInput.class);
String payload = "{\"bookInput\": { \"name\": \"test name\", \"authorId\": 42} }";
DataFetchingEnvironment environment = initEnvironment(payload);
MethodParameter methodParameter = getMethodParameter(addBook, 0);
Object result = resolver.resolveArgument(methodParameter, environment);
assertThat(result).isNotNull().isInstanceOf(KotlinBookInput.class);
assertThat((KotlinBookInput) result).hasFieldOrPropertyWithValue("name", "test name")
.hasFieldOrPropertyWithValue("authorId", 42L);
}
@Test @Test
void shouldResolveListOfJavaBeansArgument() throws Exception { void shouldResolveListOfJavaBeansArgument() throws Exception {
Method addBooks = ClassUtils.getMethod(BookController.class, "addBooks", List.class); Method addBooks = ClassUtils.getMethod(BookController.class, "addBooks", List.class);
@@ -147,6 +159,11 @@ class ArgumentMethodArgumentResolverTests {
return null; return null;
} }
@MutationMapping
public Book ktAddBook(@Argument KotlinBookInput bookInput) {
return null;
}
@MutationMapping @MutationMapping
public List<Book> addBooks(@Argument List<Book> books) { public List<Book> addBooks(@Argument List<Book> books) {
return null; return null;

View File

@@ -0,0 +1,3 @@
package org.springframework.graphql.data.method.annotation.support;
data class KotlinBookInput(val name: String, val authorId: Long)