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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user