Skip to content

Commit 6119be9

Browse files
committed
Improve optional argument support for ProjectedPayload
This commit adds support for the ArgumentValue wrapper for passing null when the argument is not present. Closes gh-550
1 parent d8b3dad commit 6119be9

File tree

2 files changed

+83
-11
lines changed

2 files changed

+83
-11
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java

+31-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.graphql.data.method.annotation.support;
1717

1818

19+
import java.util.Map;
1920
import java.util.Optional;
2021

2122
import graphql.schema.DataFetchingEnvironment;
@@ -25,6 +26,7 @@
2526
import org.springframework.core.annotation.AnnotatedElementUtils;
2627
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
2728
import org.springframework.data.web.ProjectedPayload;
29+
import org.springframework.graphql.data.ArgumentValue;
2830
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
2931
import org.springframework.graphql.data.method.annotation.Argument;
3032
import org.springframework.util.Assert;
@@ -90,20 +92,44 @@ protected SpelAwareProxyProjectionFactory getProjectionFactory() {
9092

9193
@Override
9294
public boolean supportsParameter(MethodParameter parameter) {
93-
Class<?> type = parameter.nestedIfOptional().getNestedParameterType();
95+
Class<?> type = getTargetType(parameter);
9496
return (type.isInterface() && AnnotatedElementUtils.findMergedAnnotation(type, ProjectedPayload.class) != null);
9597
}
9698

99+
private static Class<?> getTargetType(MethodParameter parameter) {
100+
Class<?> type = parameter.getParameterType();
101+
return (type.equals(Optional.class) || type.equals(ArgumentValue.class) ?
102+
parameter.nested().getNestedParameterType() : parameter.getParameterType());
103+
}
104+
97105
@Override
98106
public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) throws Exception {
99107

100108
String name = (parameter.hasParameterAnnotation(Argument.class) ?
101109
ArgumentMethodArgumentResolver.getArgumentName(parameter) : null);
102110

103-
Class<?> targetType = parameter.nestedIfOptional().getNestedParameterType();
104-
Object rawValue = (name != null ? environment.getArgument(name) : environment.getArguments());
105-
Object value = (!parameter.isOptional() || rawValue != null ? createProjection(targetType, rawValue) : null);
106-
return (parameter.isOptional() ? Optional.ofNullable(value) : value);
111+
Class<?> targetType = parameter.getParameterType();
112+
boolean isOptional = (targetType == Optional.class);
113+
boolean isArgumentValue = (targetType == ArgumentValue.class);
114+
115+
if (isOptional || isArgumentValue) {
116+
targetType = parameter.nested().getNestedParameterType();
117+
}
118+
119+
Map<String, Object> arguments = environment.getArguments();
120+
Object rawValue = (name != null ? arguments.get(name) : arguments);
121+
Object value = (rawValue != null ? createProjection(targetType, rawValue) : null);
122+
123+
if (isOptional) {
124+
return Optional.ofNullable(value);
125+
}
126+
else if (isArgumentValue) {
127+
return (name != null && arguments.containsKey(name) ?
128+
ArgumentValue.ofNullable(value) : ArgumentValue.omitted());
129+
}
130+
else {
131+
return value;
132+
}
107133
}
108134

109135
/**

spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolverTests.java

+52-6
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
import java.util.Optional;
2020

2121
import org.junit.jupiter.api.BeforeEach;
22-
import org.junit.jupiter.api.Disabled;
2322
import org.junit.jupiter.api.Test;
2423

2524
import org.springframework.context.support.StaticApplicationContext;
2625
import org.springframework.core.MethodParameter;
2726
import org.springframework.data.web.ProjectedPayload;
2827
import org.springframework.graphql.Book;
28+
import org.springframework.graphql.data.ArgumentValue;
2929
import org.springframework.graphql.data.method.annotation.Argument;
3030
import org.springframework.graphql.data.method.annotation.QueryMapping;
3131
import org.springframework.stereotype.Controller;
@@ -50,11 +50,15 @@ void setUp() {
5050

5151
@Test
5252
void supports() {
53-
MethodParameter param = methodParam(BookController.class, "optionalProjection", Optional.class);
54-
assertThat(this.resolver.supportsParameter(param)).isTrue();
53+
testSupports("projection", BookProjection.class, true);
54+
testSupports("optionalProjection", Optional.class, true);
55+
testSupports("optionalString", Optional.class, false);
56+
testSupports("argumentValueProjection", ArgumentValue.class, true);
57+
}
5558

56-
param = methodParam(BookController.class, "optionalString", Optional.class);
57-
assertThat(this.resolver.supportsParameter(param)).isFalse();
59+
void testSupports(String methodName, Class<?> methodParamType, boolean supported) {
60+
MethodParameter param = methodParam(BookController.class, methodName, methodParamType);
61+
assertThat(this.resolver.supportsParameter(param)).isEqualTo(supported);
5862
}
5963

6064
@Test
@@ -81,7 +85,44 @@ void optionalNotPresent() throws Exception {
8185
}
8286

8387
@Test
84-
@Disabled // pending decision under gh-550
88+
void argumentValuePresent() throws Exception {
89+
90+
Object result = this.resolver.resolveArgument(
91+
methodParam(BookController.class, "argumentValueProjection", ArgumentValue.class),
92+
environment("{ \"where\" : { \"author\" : \"Orwell\" }}"));
93+
94+
assertThat(result).isNotNull().isInstanceOf(ArgumentValue.class);
95+
BookProjection book = ((ArgumentValue<BookProjection>) result).value();
96+
assertThat(book.getAuthor()).isEqualTo("Orwell");
97+
}
98+
99+
@Test
100+
void argumentValueSetToNull() throws Exception {
101+
102+
Object result = this.resolver.resolveArgument(
103+
methodParam(BookController.class, "argumentValueProjection", ArgumentValue.class),
104+
environment("{ \"where\" : null}"));
105+
106+
assertThat(result).isNotNull().isInstanceOf(ArgumentValue.class);
107+
ArgumentValue<BookProjection> value = ((ArgumentValue<BookProjection>) result);
108+
assertThat(value.isPresent()).isFalse();
109+
assertThat(value.isOmitted()).isFalse();
110+
}
111+
112+
@Test
113+
void argumentValueIsOmitted() throws Exception {
114+
115+
Object result = this.resolver.resolveArgument(
116+
methodParam(BookController.class, "argumentValueProjection", ArgumentValue.class),
117+
environment("{}"));
118+
119+
assertThat(result).isNotNull().isInstanceOf(ArgumentValue.class);
120+
ArgumentValue<BookProjection> value = ((ArgumentValue<BookProjection>) result);
121+
assertThat(value.isPresent()).isFalse();
122+
assertThat(value.isOmitted()).isTrue();
123+
}
124+
125+
@Test // gh-550
85126
void nullValue() throws Exception {
86127

87128
Object result = this.resolver.resolveArgument(
@@ -110,6 +151,11 @@ public List<Book> optionalProjection(@Argument(name = "where") Optional<BookProj
110151
public void optionalString(@Argument Optional<String> projection) {
111152
}
112153

154+
@QueryMapping
155+
public List<Book> argumentValueProjection(@Argument(name = "where") ArgumentValue<BookProjection> projection) {
156+
return null;
157+
}
158+
113159
}
114160

115161

0 commit comments

Comments
 (0)