From 13683eea17bbb0dff5b07ce883cef490a9665452 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 17 Oct 2022 17:27:44 +0100 Subject: [PATCH] Support Optional for ProjectPayload arguments Closes gh-506 --- ...rojectedPayloadMethodArgumentResolver.java | 29 ++++-- ...tedPayloadMethodArgumentResolverTests.java | 93 +++++++++++++++++++ 2 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolverTests.java diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java index ed588141..ab6be6ee 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.graphql.data.method.annotation.support; +import java.util.Optional; + import graphql.schema.DataFetchingEnvironment; import org.springframework.context.ApplicationContext; @@ -77,24 +79,33 @@ public class ProjectedPayloadMethodArgumentResolver implements HandlerMethodArgu @Override public boolean supportsParameter(MethodParameter parameter) { - Class type = parameter.getParameterType(); - - if (!type.isInterface()) { - return false; - } - - return AnnotatedElementUtils.findMergedAnnotation(type, ProjectedPayload.class) != null; + Class type = parameter.nestedIfOptional().getNestedParameterType(); + return (type.isInterface() && + AnnotatedElementUtils.findMergedAnnotation(type, ProjectedPayload.class) != null); } @Override public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) throws Exception { + String name = (parameter.hasParameterAnnotation(Argument.class) ? ArgumentMethodArgumentResolver.getArgumentName(parameter) : null); + Class projectionType = parameter.getParameterType(); + + boolean isOptional = parameter.isOptional(); + if (isOptional) { + projectionType = parameter.nestedIfOptional().getNestedParameterType(); + } + Object projectionSource = (name != null ? environment.getArgument(name) : environment.getArguments()); - return project(parameter.getParameterType(), projectionSource); + Object value = null; + if (!isOptional || projectionSource != null) { + value = project(projectionType, projectionSource); + } + + return (isOptional ? Optional.ofNullable(value) : value); } protected Object project(Class projectionType, Object projectionSource){ diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolverTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolverTests.java new file mode 100644 index 00000000..22ac8108 --- /dev/null +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolverTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.data.method.annotation.support; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +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.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.stereotype.Controller; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + */ +public class ProjectedPayloadMethodArgumentResolverTests extends ArgumentResolverTestSupport { + + private ProjectedPayloadMethodArgumentResolver resolver; + + + @BeforeEach + void setUp() { + StaticApplicationContext context = new StaticApplicationContext(); + this.resolver = new ProjectedPayloadMethodArgumentResolver(context); + } + + + @Test + void supports() { + MethodParameter param = methodParam(BookController.class, "optionalProjection", Optional.class); + assertThat(this.resolver.supportsParameter(param)).isTrue(); + + param = methodParam(BookController.class, "optionalString", Optional.class); + assertThat(this.resolver.supportsParameter(param)).isFalse(); + } + + @Test + void optionalWrapper() throws Exception { + + Object result = this.resolver.resolveArgument( + methodParam(BookController.class, "optionalProjection", Optional.class), + environment("{}")); + + assertThat(result).isNotNull().isInstanceOf(Optional.class); + assertThat((Optional) result).isNotPresent(); + } + + + @SuppressWarnings({"ConstantConditions", "unused"}) + @Controller + static class BookController { + + @QueryMapping + public List optionalProjection(@Argument(name = "where") Optional projection) { + return null; + } + + @QueryMapping + public void optionalString(@Argument Optional projection) { + } + + } + + + @ProjectedPayload + interface BookProjection { + + String getAuthor(); + + } + +}