diff --git a/spring-graphql-docs/src/docs/asciidoc/index.adoc b/spring-graphql-docs/src/docs/asciidoc/index.adoc index 316bf49a..4c41e86d 100644 --- a/spring-graphql-docs/src/docs/asciidoc/index.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/index.adoc @@ -1183,19 +1183,23 @@ Schema mapping handler methods can have any of the following method arguments: | `@Argument` | For access to a named field argument bound to a higher-level, typed Object. + See <>. | `@Argument Map` -| For access to the raw map of arguments, where `@Argument` does not have a -`name` attribute. +| For access to the raw argument value. + +See <>. | `ArgumentValue` | For access to a named field argument bound to a higher-level, typed Object along with a flag to indicate if the input argument was omitted vs set to `null`. + See <>. | `@Arguments` | For access to all field arguments bound to a higher-level, typed Object. + See <>. | `@Arguments Map` @@ -1203,14 +1207,17 @@ See <>. | `@ProjectedPayload` Interface | For access to field arguments through a project interface. + See <>. | "Source" | For access to the source (i.e. parent/container) instance of the field. + See <>. | `DataLoader` | For access to a `DataLoader` in the `DataLoaderRegistry`. + See <>. | `@ContextValue` @@ -1290,8 +1297,26 @@ are enforced by GraphQL Java. If binding fails, a `BindException` is raised with binding issues accumulated as field errors where the `field` of each error is the argument path where the issue occurred. -You can use `@Argument` with a `Map` argument, to obtain the raw map of -all argument values. The name attribute on `@Argument` must not be set. +You can use `@Argument` with a `Map` argument, to obtain the raw value of +the argument. For example: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Controller + public class BookController { + + @MutationMapping + public Book addBook(@Argument Map bookInput) { + // ... + } + } +---- + +NOTE: Prior to 1.2, `@Argument Map` returned the full arguments map if +the annotation did not specify a name. After 1.2, `@Argument` with +`Map` always returns the raw argument value, matching either to the name +specified in the annotation, or to the parameter name. For access to the full arguments +map, please use <> instead. [[controllers.schema-mapping.argument-value]] diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/Argument.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/Argument.java index a2c0561e..6e378f82 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/Argument.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/Argument.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -40,12 +40,13 @@ import org.springframework.validation.BindException; * {@code field} of each error is the argument path where the issue occurred. * *

If the method parameter is {@link java.util.Map Map<String, Object>} - * and a parameter name is not specified, then the resolves value is the raw - * {@link graphql.schema.DataFetchingEnvironment#getArguments() arguments} map. + * then the raw argument value for the named argument is used. For access to the + * full {@link graphql.schema.DataFetchingEnvironment#getArguments() arguments} + * map, use {@link Arguments @Arguments} instead. * - *

Note that this annotation has neither a "required" flag nor the option to - * specify a default value, both of which can be specified at the GraphQL schema - * level and are enforced by the GraphQL Java engine. + *

This annotation has neither a "required" flag nor the option to specify a + * default value, both of which can be specified at the GraphQL schema level + * and are enforced by the GraphQL Java engine. * * @author Rossen Stoyanchev * @since 1.0.0 diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java index da700d77..81142a41 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java @@ -189,7 +189,6 @@ public class AnnotatedControllerConfigurer // Must be ahead of ArgumentMethodArgumentResolver resolvers.addResolver(new ProjectedPayloadMethodArgumentResolver(obtainApplicationContext())); } - resolvers.addResolver(new ArgumentMapMethodArgumentResolver()); GraphQlArgumentBinder argumentBinder = new GraphQlArgumentBinder(this.conversionService); resolvers.addResolver(new ArgumentMethodArgumentResolver(argumentBinder)); resolvers.addResolver(new ArgumentsMethodArgumentResolver(argumentBinder)); diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ArgumentMapMethodArgumentResolver.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ArgumentMapMethodArgumentResolver.java deleted file mode 100644 index 8699cd04..00000000 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ArgumentMapMethodArgumentResolver.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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. - * 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.Map; - -import graphql.schema.DataFetchingEnvironment; - -import org.springframework.core.MethodParameter; -import org.springframework.graphql.data.method.HandlerMethodArgumentResolver; -import org.springframework.graphql.data.method.annotation.Argument; -import org.springframework.graphql.data.method.annotation.Arguments; -import org.springframework.util.StringUtils; - - -/** - * Resolves a {@link Map} method parameter for access to the raw arguments map. - * Supported with the following: - *

    - *
  • {@link Map} argument annotated with {@link Argument @Argument} where the - * annotation does not explicitly specify a name. - *
  • {@link Map} argument annotated with {@link Arguments @Arguments}. - *
- * - * @author Rossen Stoyanchev - * @since 1.0.0 - */ -public class ArgumentMapMethodArgumentResolver implements HandlerMethodArgumentResolver { - - @Override - public boolean supportsParameter(MethodParameter parameter) { - return (checkArgumentMap(parameter) || checkArgumentsMap(parameter)); - } - - private static boolean checkArgumentMap(MethodParameter parameter) { - Argument argument = parameter.getParameterAnnotation(Argument.class); - return (argument != null && - Map.class.isAssignableFrom(parameter.getParameterType()) && - !StringUtils.hasText(argument.name())); - } - - private static boolean checkArgumentsMap(MethodParameter parameter) { - Arguments argument = parameter.getParameterAnnotation(Arguments.class); - return (argument != null && Map.class.isAssignableFrom(parameter.getParameterType())); - } - - @Override - public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) { - return environment.getArguments(); - } - -} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ArgumentMethodArgumentResolver.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ArgumentMethodArgumentResolver.java index 87f49bf7..854591d4 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ArgumentMethodArgumentResolver.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ArgumentMethodArgumentResolver.java @@ -30,7 +30,7 @@ import org.springframework.util.StringUtils; * Resolver for a method parameter that is annotated with * {@link Argument @Argument}. The specified raw argument value is obtained via * {@link DataFetchingEnvironment#getArgument(String)} and bound to a higher - * level object, via {@link GraphQlArgumentBinder}, to match the target method + * level object via {@link GraphQlArgumentBinder} to match the target method * parameter type. * *

This resolver also supports wrapping the target object with @@ -45,9 +45,7 @@ import org.springframework.util.StringUtils; * @author Rossen Stoyanchev * @author Brian Clozel * @since 1.0.0 - * @see org.springframework.graphql.data.method.annotation.Argument - * @see org.springframework.graphql.data.method.annotation.Arguments - * @see org.springframework.graphql.data.GraphQlArgumentBinder + * @see org.springframework.graphql.data.method.annotation.support.ArgumentsMethodArgumentResolver */ public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentResolver { @@ -81,7 +79,8 @@ public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentReso } } else if (parameter.getParameterType() != ArgumentValue.class) { - throw new IllegalStateException("Expected @Argument annotation"); + throw new IllegalStateException( + "Expected either @Argument or a method parameter of type ArgumentValue"); } String parameterName = parameter.getParameterName(); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentMapMethodArgumentResolverTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentMapMethodArgumentResolverTests.java deleted file mode 100644 index fbd8575f..00000000 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentMapMethodArgumentResolverTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.Collections; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.MethodParameter; -import org.springframework.graphql.Book; -import org.springframework.graphql.data.method.HandlerMethodArgumentResolver; -import org.springframework.graphql.data.method.annotation.Argument; -import org.springframework.graphql.data.method.annotation.Arguments; -import org.springframework.graphql.data.method.annotation.QueryMapping; -import org.springframework.stereotype.Controller; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link ArgumentMethodArgumentResolver}. - * @author Rossen Stoyanchev - */ -class ArgumentMapMethodArgumentResolverTests extends ArgumentResolverTestSupport { - - private final HandlerMethodArgumentResolver resolver = new ArgumentMapMethodArgumentResolver(); - - - @Test - void shouldSupportAnnotatedParameters() { - MethodParameter param = methodParam(BookController.class, "argumentMap", Map.class); - assertThat(this.resolver.supportsParameter(param)).isTrue(); - - param = methodParam(BookController.class, "argumentsMap", Map.class); - assertThat(this.resolver.supportsParameter(param)).isTrue(); - - param = methodParam(BookController.class, "argument", Long.class); - assertThat(this.resolver.supportsParameter(param)).isFalse(); - - param = methodParam(BookController.class, "namedArgumentMap", Map.class); - assertThat(this.resolver.supportsParameter(param)).isFalse(); - - param = methodParam(BookController.class, "notAnnotated", String.class); - assertThat(this.resolver.supportsParameter(param)).isFalse(); - } - - @Test - void shouldResolveRawArgumentsMap() throws Exception { - Object result = this.resolver.resolveArgument( - methodParam(BookController.class, "argumentMap", Map.class), - environment("{\"id\": 42 }")); - - assertThat(result).isNotNull().isInstanceOf(Map.class).isEqualTo(Collections.singletonMap("id", 42)); - } - - - @SuppressWarnings({"ConstantConditions", "unused"}) - @Controller - static class BookController { - - @QueryMapping - public Book argumentMap(@Argument Map args) { - return null; - } - - @QueryMapping - public Book argumentsMap(@Arguments Map args) { - return null; - } - - @QueryMapping - public Book argument(@Argument Long id) { - return null; - } - - @QueryMapping - public Book namedArgumentMap(@Argument(name = "book") Map book) { - return null; - } - - public void notAnnotated(String param) { - } - - } - -} \ No newline at end of file diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentMethodArgumentResolverTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentMethodArgumentResolverTests.java index 0aba8a85..10483d77 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentMethodArgumentResolverTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2023 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. @@ -18,6 +18,7 @@ package org.springframework.graphql.data.method.annotation.support; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; @@ -52,6 +53,9 @@ class ArgumentMethodArgumentResolverTests extends ArgumentResolverTestSupport { param = methodParam(BookController.class, "addBook", ArgumentValue.class); assertThat(this.resolver.supportsParameter(param)).isTrue(); + param = methodParam(BookController.class, "rawArgumentValue", Map.class); + assertThat(this.resolver.supportsParameter(param)).isTrue(); + param = methodParam(BookController.class, "notSupported", String.class); assertThat(this.resolver.supportsParameter(param)).isFalse(); } @@ -113,6 +117,17 @@ class ArgumentMethodArgumentResolverTests extends ArgumentResolverTestSupport { assertThat(result).isNotNull().isInstanceOf(Keyword.class).hasFieldOrPropertyWithValue("term", "test"); } + @Test + void shouldResolveRawArgumentValue() throws Exception { + Map result = (Map) this.resolver.resolveArgument( + methodParam(BookController.class, "rawArgumentValue", Map.class), + environment("{\"bookInput\": { \"name\": \"test name\", \"authorId\": 42} }")); + + assertThat(result) + .containsEntry("name", "test name") + .containsEntry("authorId", 42); + } + @SuppressWarnings({"ConstantConditions", "unused"}) @Controller @@ -147,8 +162,13 @@ class ArgumentMethodArgumentResolverTests extends ArgumentResolverTestSupport { return null; } + @MutationMapping + public Book rawArgumentValue(@Argument Map bookInput) { + return null; + } } + @SuppressWarnings({"NotNullFieldNotInitialized", "unused"}) static class BookInput { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentsMethodArgumentResolverTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentsMethodArgumentResolverTests.java index f05d69c3..a231425d 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentsMethodArgumentResolverTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ArgumentsMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2023 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. @@ -17,6 +17,9 @@ package org.springframework.graphql.data.method.annotation.support; +import java.util.Collections; +import java.util.Map; + import org.junit.jupiter.api.Test; import org.springframework.core.MethodParameter; @@ -27,6 +30,7 @@ import org.springframework.graphql.data.GraphQlArgumentBinder; import org.springframework.graphql.data.method.HandlerMethodArgumentResolver; import org.springframework.graphql.data.method.annotation.Arguments; import org.springframework.graphql.data.method.annotation.MutationMapping; +import org.springframework.graphql.data.method.annotation.QueryMapping; import org.springframework.stereotype.Controller; import static org.assertj.core.api.Assertions.assertThat; @@ -45,6 +49,9 @@ class ArgumentsMethodArgumentResolverTests extends ArgumentResolverTestSupport { void shouldSupportAnnotatedParameters() { MethodParameter methodParameter = methodParam(BookController.class, "addBook", BookInput.class); assertThat(resolver.supportsParameter(methodParameter)).isTrue(); + + methodParameter = methodParam(BookController.class, "argumentsMap", Map.class); + assertThat(resolver.supportsParameter(methodParameter)).isTrue(); } @Test @@ -71,6 +78,15 @@ class ArgumentsMethodArgumentResolverTests extends ArgumentResolverTestSupport { }); } + @Test + void shouldResolveRawArgumentsMap() throws Exception { + Object result = this.resolver.resolveArgument( + methodParam(BookController.class, "argumentsMap", Map.class), + environment("{\"id\": 42 }")); + + assertThat(result).isNotNull().isEqualTo(Collections.singletonMap("id", 42)); + } + @SuppressWarnings({"ConstantConditions", "unused"}) @Controller @@ -84,6 +100,10 @@ class ArgumentsMethodArgumentResolverTests extends ArgumentResolverTestSupport { return null; } + @QueryMapping + public Book argumentsMap(@Arguments Map map) { + return null; + } } @SuppressWarnings({"NotNullFieldNotInitialized", "unused"})