Skip to content

Commit d6c8558

Browse files
committed
Merge branch '1.0.x'
2 parents a69b7bc + 8490417 commit d6c8558

File tree

5 files changed

+194
-28
lines changed

5 files changed

+194
-28
lines changed

spring-graphql-docs/src/docs/asciidoc/index.adoc

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -339,10 +339,14 @@ parsed and merged together. That means schema files can be loaded from just abou
339339
location.
340340

341341
By default, the Spring Boot starter
342-
{spring-boot-ref-docs}/web.html#web.graphql.schema[finds schema files] from a
343-
well-known classpath location, but you can change that to a location on the file system
344-
via `FileSystemResource`, to byte content via `ByteArrayResource`, or implement a custom
345-
`Resource` that loads schema files from a remote location or storage.
342+
{spring-boot-ref-docs}/web.html#web.graphql.schema[looks for schema files] with extensions
343+
".graphqls" or ".gqls" under the location `classpath:graphql/**`, which is typically
344+
`src/main/resources/graphql`. You can also use a file system location, or any location
345+
supported by the Spring `Resource` hierarchy, including a custom implementation that
346+
loads schema files from remote locations, from storage, or from memory.
347+
348+
TIP: Use `classpath*:graphql/**/` to find schema files across multiple classpath
349+
locations, e.g. across multiple modules.
346350

347351

348352
[[execution-graphqlsource-schema-creation]]
@@ -1272,11 +1276,18 @@ See <<controllers-schema-mapping-argument>>.
12721276
| For access to all field arguments bound to a higher-level, typed Object.
12731277
See <<controllers-schema-mapping-arguments>>.
12741278

1279+
| `@Argument Map<String, Object>`
1280+
| For access to the raw map of arguments, where `@Argument` does not have a
1281+
`name` attribute.
1282+
1283+
| `@Arguments Map<String, Object>`
1284+
| For access to the raw map of arguments.
1285+
12751286
| `@ProjectedPayload` Interface
12761287
| For access to field arguments through a project interface.
12771288
See <<controllers-schema-mapping-projectedpayload-argument>>.
12781289

1279-
| Source
1290+
| "Source"
12801291
| For access to the source (i.e. parent/container) instance of the field.
12811292
See <<controllers-schema-mapping-source>>.
12821293

@@ -1362,8 +1373,8 @@ are enforced by GraphQL Java.
13621373
If binding fails, a `BindException` is raised with binding issues accumulated as field
13631374
errors where the `field` of each error is the argument path where the issue occurred.
13641375

1365-
You can use `@Argument` on a `Map<String, Object>` argument, to obtain all argument
1366-
values. The name attribute on `@Argument` must not be set.
1376+
You can use `@Argument` with a `Map<String, Object>` argument, to obtain the raw map of
1377+
all argument values. The name attribute on `@Argument` must not be set.
13671378

13681379

13691380

@@ -1377,6 +1388,8 @@ For example, `@Argument BookInput bookInput` uses the value of the argument "boo
13771388
to initialize `BookInput`, while `@Arguments` uses the full arguments map and in that
13781389
case, top-level arguments are bound to `BookInput` properties.
13791390

1391+
You can use `@Arguments` with a `Map<String, Object>` argument, to obtain the raw map of
1392+
all argument values.
13801393

13811394

13821395
[[controllers-schema-mapping-projectedpayload-argument]]

spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentBinder.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ private Object createValue(
248248
Object target;
249249
Constructor<?> ctor = BeanUtils.getResolvableConstructor(targetType);
250250

251-
// Default constructor with data binding
251+
// Default constructor + data binding via properties
252252

253253
if (ctor.getParameterCount() == 0) {
254254
target = BeanUtils.instantiateClass(ctor);
@@ -284,6 +284,9 @@ private Object createValue(
284284
if (rawValue == null && methodParam.isOptional()) {
285285
args[i] = (paramTypes[i] == Optional.class ? Optional.empty() : null);
286286
}
287+
else if (paramTypes[i] == Object.class) {
288+
args[i] = rawValue;
289+
}
287290
else if (isApproximableCollectionType(rawValue)) {
288291
ResolvableType elementType = ResolvableType.forMethodParameter(methodParam);
289292
args[i] = createCollection((Collection<Object>) rawValue, elementType, bindingResult, segments);

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,18 @@
2323
import org.springframework.core.MethodParameter;
2424
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
2525
import org.springframework.graphql.data.method.annotation.Argument;
26+
import org.springframework.graphql.data.method.annotation.Arguments;
2627
import org.springframework.util.StringUtils;
2728

2829

2930
/**
30-
* Resolves a {@link Map} method parameter annotated with an
31-
* {@link Argument @Argument} by returning the GraphQL
32-
* {@link DataFetchingEnvironment#getArguments() arguments} map.
31+
* Resolves a {@link Map} method parameter for access to the raw arguments map.
32+
* Supported with the following:
33+
* <ul>
34+
* <li>{@link Map} argument annotated with {@link Argument @Argument} where the
35+
* annotation does not explicitly specify a name.
36+
* <li>{@link Map} argument annotated with {@link Arguments @Arguments}.
37+
* </ul>
3338
*
3439
* @author Rossen Stoyanchev
3540
* @since 1.0.0
@@ -38,12 +43,21 @@ public class ArgumentMapMethodArgumentResolver implements HandlerMethodArgumentR
3843

3944
@Override
4045
public boolean supportsParameter(MethodParameter parameter) {
46+
return (checkArgumentMap(parameter) || checkArgumentsMap(parameter));
47+
}
48+
49+
private static boolean checkArgumentMap(MethodParameter parameter) {
4150
Argument argument = parameter.getParameterAnnotation(Argument.class);
4251
return (argument != null &&
4352
Map.class.isAssignableFrom(parameter.getParameterType()) &&
4453
!StringUtils.hasText(argument.name()));
4554
}
4655

56+
private static boolean checkArgumentsMap(MethodParameter parameter) {
57+
Arguments argument = parameter.getParameterAnnotation(Arguments.class);
58+
return (argument != null && Map.class.isAssignableFrom(parameter.getParameterType()));
59+
}
60+
4761
@Override
4862
public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) {
4963
return environment.getArguments();

spring-graphql/src/test/java/org/springframework/graphql/data/GraphQlArgumentBinderTests.java

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,21 @@ void primaryConstructorBindingErrorWithNestedBeanList() {
280280
});
281281
}
282282

283+
@Test // gh-447
284+
@SuppressWarnings("unchecked")
285+
void primaryConstructorWithGenericObject() throws Exception {
286+
287+
Object result = this.binder.bind(
288+
environment("{\"key\":{\"value\":[{\"name\":\"first\"},{\"name\":\"second\"}]}}"), "key",
289+
ResolvableType.forClass(ObjectHolder.class));
290+
291+
assertThat(result).isNotNull().isInstanceOf(ObjectHolder.class);
292+
List<Map<Object, Object>> list = (List<Map<Object, Object>>) ((ObjectHolder) result).getValue();
293+
assertThat(list).hasSize(2).containsExactly(
294+
Collections.singletonMap("name", "first"),
295+
Collections.singletonMap("name", "second"));
296+
}
297+
283298
@Test // gh-410
284299
@SuppressWarnings("unchecked")
285300
void coercionWithSingletonList() throws Exception {
@@ -332,6 +347,7 @@ private DataFetchingEnvironment environment(String jsonPayload) throws JsonProce
332347
}
333348

334349

350+
@SuppressWarnings("unused")
335351
static class SimpleBean {
336352

337353
private String name;
@@ -434,6 +450,7 @@ public Optional<Item> getItem() {
434450
}
435451

436452

453+
@SuppressWarnings("unused")
437454
static class NoPrimaryConstructorBean {
438455

439456
NoPrimaryConstructorBean(String name) {
@@ -444,6 +461,7 @@ static class NoPrimaryConstructorBean {
444461
}
445462

446463

464+
@SuppressWarnings("unused")
447465
static class ItemListHolder {
448466

449467
private List<Item> items;
@@ -458,6 +476,40 @@ public void setItems(List<Item> items) {
458476
}
459477

460478

479+
@SuppressWarnings("unused")
480+
static class ItemSetHolder {
481+
482+
private Set<Item> items;
483+
484+
public ItemSetHolder(Set<Item> items) {
485+
this.items = items;
486+
}
487+
488+
public Set<Item> getItems() {
489+
return items;
490+
}
491+
492+
public void setItems(Set<Item> items) {
493+
this.items = items;
494+
}
495+
}
496+
497+
498+
static class ObjectHolder {
499+
500+
private final Object value;
501+
502+
ObjectHolder(Object value) {
503+
this.value = value;
504+
}
505+
506+
public Object getValue() {
507+
return value;
508+
}
509+
}
510+
511+
512+
@SuppressWarnings("unused")
461513
static class Item {
462514

463515
private String name;
@@ -494,21 +546,4 @@ public int hashCode() {
494546
}
495547
}
496548

497-
static class ItemSetHolder {
498-
499-
private Set<Item> items;
500-
501-
public ItemSetHolder(Set<Item> items) {
502-
this.items = items;
503-
}
504-
505-
public Set<Item> getItems() {
506-
return items;
507-
}
508-
509-
public void setItems(Set<Item> items) {
510-
this.items = items;
511-
}
512-
}
513-
514549
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2020-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql.data.method.annotation.support;
18+
19+
20+
import java.util.Collections;
21+
import java.util.Map;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.core.MethodParameter;
26+
import org.springframework.graphql.Book;
27+
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
28+
import org.springframework.graphql.data.method.annotation.Argument;
29+
import org.springframework.graphql.data.method.annotation.Arguments;
30+
import org.springframework.graphql.data.method.annotation.QueryMapping;
31+
import org.springframework.stereotype.Controller;
32+
33+
import static org.assertj.core.api.Assertions.assertThat;
34+
35+
/**
36+
* Unit tests for {@link ArgumentMethodArgumentResolver}.
37+
* @author Rossen Stoyanchev
38+
*/
39+
class ArgumentMapMethodArgumentResolverTests extends ArgumentResolverTestSupport {
40+
41+
private final HandlerMethodArgumentResolver resolver = new ArgumentMapMethodArgumentResolver();
42+
43+
44+
@Test
45+
void shouldSupportAnnotatedParameters() {
46+
MethodParameter param = methodParam(BookController.class, "argumentMap", Map.class);
47+
assertThat(this.resolver.supportsParameter(param)).isTrue();
48+
49+
param = methodParam(BookController.class, "argumentsMap", Map.class);
50+
assertThat(this.resolver.supportsParameter(param)).isTrue();
51+
52+
param = methodParam(BookController.class, "argument", Long.class);
53+
assertThat(this.resolver.supportsParameter(param)).isFalse();
54+
55+
param = methodParam(BookController.class, "namedArgumentMap", Map.class);
56+
assertThat(this.resolver.supportsParameter(param)).isFalse();
57+
58+
param = methodParam(BookController.class, "notAnnotated", String.class);
59+
assertThat(this.resolver.supportsParameter(param)).isFalse();
60+
}
61+
62+
@Test
63+
void shouldResolveRawArgumentsMap() throws Exception {
64+
Object result = this.resolver.resolveArgument(
65+
methodParam(BookController.class, "argumentMap", Map.class),
66+
environment("{\"id\": 42 }"));
67+
68+
assertThat(result).isNotNull().isInstanceOf(Map.class).isEqualTo(Collections.singletonMap("id", 42));
69+
}
70+
71+
72+
@SuppressWarnings({"ConstantConditions", "unused"})
73+
@Controller
74+
static class BookController {
75+
76+
@QueryMapping
77+
public Book argumentMap(@Argument Map<?, ?> args) {
78+
return null;
79+
}
80+
81+
@QueryMapping
82+
public Book argumentsMap(@Arguments Map<?, ?> args) {
83+
return null;
84+
}
85+
86+
@QueryMapping
87+
public Book argument(@Argument Long id) {
88+
return null;
89+
}
90+
91+
@QueryMapping
92+
public Book namedArgumentMap(@Argument(name = "book") Map<?, ?> book) {
93+
return null;
94+
}
95+
96+
public void notAnnotated(String param) {
97+
}
98+
99+
}
100+
101+
}

0 commit comments

Comments
 (0)