Skip to content

Commit 2f598ed

Browse files
committed
Support Optional constructor arg for argument binding
Closes gh-470
1 parent 257f309 commit 2f598ed

File tree

2 files changed

+53
-5
lines changed

2 files changed

+53
-5
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentBinder.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,10 @@ private Object wrapAsOptionalIfNecessary(@Nullable Object value, ResolvableType
188188
return (type.resolve(Object.class).equals(Optional.class) ? Optional.ofNullable(value) : value);
189189
}
190190

191-
private boolean isApproximableCollectionType(Object rawValue) {
192-
return (CollectionFactory.isApproximableCollectionType(rawValue.getClass()) ||
193-
rawValue instanceof List); // it may be SingletonList
191+
private boolean isApproximableCollectionType(@Nullable Object rawValue) {
192+
return (rawValue != null &&
193+
(CollectionFactory.isApproximableCollectionType(rawValue.getClass()) ||
194+
rawValue instanceof List)); // it may be SingletonList
194195
}
195196

196197
@SuppressWarnings({"ConstantConditions", "unchecked"})
@@ -283,12 +284,15 @@ private Object createValue(
283284
if (rawValue == null && methodParam.isOptional()) {
284285
args[i] = (paramTypes[i] == Optional.class ? Optional.empty() : null);
285286
}
286-
else if (rawValue != null && isApproximableCollectionType(rawValue)) {
287+
else if (isApproximableCollectionType(rawValue)) {
287288
ResolvableType elementType = ResolvableType.forMethodParameter(methodParam);
288289
args[i] = createCollection((Collection<Object>) rawValue, elementType, bindingResult, segments);
289290
}
290291
else if (rawValue instanceof Map) {
291-
args[i] = createValueOrNull((Map<String, Object>) rawValue, paramTypes[i], bindingResult, segments);
292+
boolean isOptional = (paramTypes[i] == Optional.class);
293+
Class<?> type = (isOptional ? methodParam.nestedIfOptional().getNestedParameterType() : paramTypes[i]);
294+
Object value = createValueOrNull((Map<String, Object>) rawValue, type, bindingResult, segments);
295+
args[i] = (isOptional ? Optional.ofNullable(value) : value);
292296
}
293297
else {
294298
args[i] = convertValue(rawValue, paramTypes[i], new TypeDescriptor(methodParam), bindingResult, segments);

spring-graphql/src/test/java/org/springframework/graphql/data/GraphQlArgumentBinderTests.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.List;
2222
import java.util.Map;
2323
import java.util.Objects;
24+
import java.util.Optional;
2425
import java.util.Set;
2526
import java.util.stream.Collectors;
2627
import java.util.stream.IntStream;
@@ -32,6 +33,7 @@
3233
import org.junit.jupiter.api.Test;
3334

3435
import org.springframework.core.ResolvableType;
36+
import org.springframework.format.support.DefaultFormattingConversionService;
3537
import org.springframework.graphql.Book;
3638
import org.springframework.validation.BindException;
3739
import org.springframework.validation.FieldError;
@@ -182,6 +184,26 @@ void primaryConstructorWithBeanArgument() throws Exception {
182184
assertThat(((PrimaryConstructorItemBean) result).getAge()).isEqualTo(30);
183185
}
184186

187+
@Test
188+
void primaryConstructorWithOptionalBeanArgument() throws Exception {
189+
190+
GraphQlArgumentBinder argumentBinder =
191+
new GraphQlArgumentBinder(new DefaultFormattingConversionService());
192+
193+
Object result = argumentBinder.bind(
194+
environment(
195+
"{\"key\":{" +
196+
"\"item\":{\"name\":\"Item name\"}," +
197+
"\"name\":\"Hello\"," +
198+
"\"age\":\"30\"}}"),
199+
"key",
200+
ResolvableType.forClass(PrimaryConstructorOptionalItemBean.class));
201+
202+
assertThat(result).isNotNull().isInstanceOf(PrimaryConstructorOptionalItemBean.class);
203+
assertThat(((PrimaryConstructorOptionalItemBean) result).getItem().get().getName()).isEqualTo("Item name");
204+
assertThat(((PrimaryConstructorOptionalItemBean) result).getName().get()).isEqualTo("Hello");
205+
}
206+
185207
@Test
186208
void primaryConstructorWithNestedBeanList() throws Exception {
187209

@@ -390,6 +412,28 @@ public List<Item> getItems() {
390412
}
391413

392414

415+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
416+
static class PrimaryConstructorOptionalItemBean {
417+
418+
private final Optional<String> name;
419+
420+
private final Optional<Item> item;
421+
422+
public PrimaryConstructorOptionalItemBean(Optional<String> name, Optional<Item> item) {
423+
this.name = name;
424+
this.item = item;
425+
}
426+
427+
public Optional<String> getName() {
428+
return this.name;
429+
}
430+
431+
public Optional<Item> getItem() {
432+
return item;
433+
}
434+
}
435+
436+
393437
static class NoPrimaryConstructorBean {
394438

395439
NoPrimaryConstructorBean(String name) {

0 commit comments

Comments
 (0)