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
This commit is contained in:
rstoyanchev
2022-12-01 12:53:04 +00:00
parent d8b3dad505
commit 6119be935a
2 changed files with 83 additions and 11 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.graphql.data.method.annotation.support;
import java.util.Map;
import java.util.Optional;
import graphql.schema.DataFetchingEnvironment;
@@ -25,6 +26,7 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.web.ProjectedPayload;
import org.springframework.graphql.data.ArgumentValue;
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.util.Assert;
@@ -90,20 +92,44 @@ public class ProjectedPayloadMethodArgumentResolver implements HandlerMethodArgu
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> type = parameter.nestedIfOptional().getNestedParameterType();
Class<?> type = getTargetType(parameter);
return (type.isInterface() && AnnotatedElementUtils.findMergedAnnotation(type, ProjectedPayload.class) != null);
}
private static Class<?> getTargetType(MethodParameter parameter) {
Class<?> type = parameter.getParameterType();
return (type.equals(Optional.class) || type.equals(ArgumentValue.class) ?
parameter.nested().getNestedParameterType() : parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) throws Exception {
String name = (parameter.hasParameterAnnotation(Argument.class) ?
ArgumentMethodArgumentResolver.getArgumentName(parameter) : null);
Class<?> targetType = parameter.nestedIfOptional().getNestedParameterType();
Object rawValue = (name != null ? environment.getArgument(name) : environment.getArguments());
Object value = (!parameter.isOptional() || rawValue != null ? createProjection(targetType, rawValue) : null);
return (parameter.isOptional() ? Optional.ofNullable(value) : value);
Class<?> targetType = parameter.getParameterType();
boolean isOptional = (targetType == Optional.class);
boolean isArgumentValue = (targetType == ArgumentValue.class);
if (isOptional || isArgumentValue) {
targetType = parameter.nested().getNestedParameterType();
}
Map<String, Object> arguments = environment.getArguments();
Object rawValue = (name != null ? arguments.get(name) : arguments);
Object value = (rawValue != null ? createProjection(targetType, rawValue) : null);
if (isOptional) {
return Optional.ofNullable(value);
}
else if (isArgumentValue) {
return (name != null && arguments.containsKey(name) ?
ArgumentValue.ofNullable(value) : ArgumentValue.omitted());
}
else {
return value;
}
}
/**

View File

@@ -19,13 +19,13 @@ import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.data.web.ProjectedPayload;
import org.springframework.graphql.Book;
import org.springframework.graphql.data.ArgumentValue;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
@@ -50,11 +50,15 @@ public class ProjectedPayloadMethodArgumentResolverTests extends ArgumentResolve
@Test
void supports() {
MethodParameter param = methodParam(BookController.class, "optionalProjection", Optional.class);
assertThat(this.resolver.supportsParameter(param)).isTrue();
testSupports("projection", BookProjection.class, true);
testSupports("optionalProjection", Optional.class, true);
testSupports("optionalString", Optional.class, false);
testSupports("argumentValueProjection", ArgumentValue.class, true);
}
param = methodParam(BookController.class, "optionalString", Optional.class);
assertThat(this.resolver.supportsParameter(param)).isFalse();
void testSupports(String methodName, Class<?> methodParamType, boolean supported) {
MethodParameter param = methodParam(BookController.class, methodName, methodParamType);
assertThat(this.resolver.supportsParameter(param)).isEqualTo(supported);
}
@Test
@@ -81,7 +85,44 @@ public class ProjectedPayloadMethodArgumentResolverTests extends ArgumentResolve
}
@Test
@Disabled // pending decision under gh-550
void argumentValuePresent() throws Exception {
Object result = this.resolver.resolveArgument(
methodParam(BookController.class, "argumentValueProjection", ArgumentValue.class),
environment("{ \"where\" : { \"author\" : \"Orwell\" }}"));
assertThat(result).isNotNull().isInstanceOf(ArgumentValue.class);
BookProjection book = ((ArgumentValue<BookProjection>) result).value();
assertThat(book.getAuthor()).isEqualTo("Orwell");
}
@Test
void argumentValueSetToNull() throws Exception {
Object result = this.resolver.resolveArgument(
methodParam(BookController.class, "argumentValueProjection", ArgumentValue.class),
environment("{ \"where\" : null}"));
assertThat(result).isNotNull().isInstanceOf(ArgumentValue.class);
ArgumentValue<BookProjection> value = ((ArgumentValue<BookProjection>) result);
assertThat(value.isPresent()).isFalse();
assertThat(value.isOmitted()).isFalse();
}
@Test
void argumentValueIsOmitted() throws Exception {
Object result = this.resolver.resolveArgument(
methodParam(BookController.class, "argumentValueProjection", ArgumentValue.class),
environment("{}"));
assertThat(result).isNotNull().isInstanceOf(ArgumentValue.class);
ArgumentValue<BookProjection> value = ((ArgumentValue<BookProjection>) result);
assertThat(value.isPresent()).isFalse();
assertThat(value.isOmitted()).isTrue();
}
@Test // gh-550
void nullValue() throws Exception {
Object result = this.resolver.resolveArgument(
@@ -110,6 +151,11 @@ public class ProjectedPayloadMethodArgumentResolverTests extends ArgumentResolve
public void optionalString(@Argument Optional<String> projection) {
}
@QueryMapping
public List<Book> argumentValueProjection(@Argument(name = "where") ArgumentValue<BookProjection> projection) {
return null;
}
}