Skip to content

Commit 7e602f0

Browse files
committed
Merge branch '1.3.x'
2 parents 175cf6f + e179030 commit 7e602f0

File tree

7 files changed

+153
-33
lines changed

7 files changed

+153
-33
lines changed

spring-graphql-docs/modules/ROOT/pages/controllers.adoc

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -920,37 +920,14 @@ Use `@GraphQlExceptionHandler` methods to handle exceptions from data fetching w
920920
flexible xref:controllers.adoc#controllers.exception-handler.signature[method signature]. When declared in a
921921
controller, exception handler methods apply to exceptions from the same controller:
922922

923-
[source,java,indent=0,subs="verbatim,quotes"]
924-
----
925-
@Controller
926-
public class BookController {
927-
928-
@QueryMapping
929-
public Book bookById(@Argument Long id) {
930-
// ...
931-
}
932-
933-
@GraphQlExceptionHandler
934-
public GraphQLError handle(BindException ex) {
935-
return GraphQLError.newError().errorType(ErrorType.BAD_REQUEST).message("...").build();
936-
}
937-
}
938-
----
923+
include-code::BookController[]
939924

940925
When declared in an `@ControllerAdvice`, exception handler methods apply across controllers:
941926

942-
[source,java,indent=0,subs="verbatim,quotes"]
943-
----
944-
@ControllerAdvice
945-
public class GlobalExceptionHandler {
927+
include-code::GlobalExceptionHandler[]
946928

947-
@GraphQlExceptionHandler
948-
public GraphQLError handle(BindException ex) {
949-
return GraphQLError.newError().errorType(ErrorType.BAD_REQUEST).message("...").build();
950-
}
951-
952-
}
953-
----
929+
As shown in the examples above, you should build errors by injecting `GraphQlErrorBuilder` in the method signature
930+
because it's been prepared with the current `DataFetchingEnvironment`.
954931

955932
Exception handling via `@GraphQlExceptionHandler` methods is applied automatically to
956933
controller invocations. To handle exceptions from other `graphql.schema.DataFetcher`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2020-2025 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.docs.controllers.exceptionhandler;
18+
19+
public record Book() {
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2020-2025 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.docs.controllers.exceptionhandler;
18+
19+
20+
import graphql.GraphQLError;
21+
import graphql.GraphqlErrorBuilder;
22+
23+
import org.springframework.graphql.data.method.annotation.Argument;
24+
import org.springframework.graphql.data.method.annotation.GraphQlExceptionHandler;
25+
import org.springframework.graphql.data.method.annotation.QueryMapping;
26+
import org.springframework.graphql.execution.ErrorType;
27+
import org.springframework.stereotype.Controller;
28+
import org.springframework.validation.BindException;
29+
30+
@Controller
31+
public class BookController {
32+
33+
@QueryMapping
34+
public Book bookById(@Argument Long id) {
35+
return /**/ new Book();
36+
}
37+
38+
@GraphQlExceptionHandler
39+
public GraphQLError handle(GraphqlErrorBuilder<?> errorBuilder, BindException ex) {
40+
return errorBuilder
41+
.errorType(ErrorType.BAD_REQUEST)
42+
.message(ex.getMessage())
43+
.build();
44+
}
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2020-2025 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.docs.controllers.exceptionhandler;
18+
19+
import graphql.GraphQLError;
20+
import graphql.GraphqlErrorBuilder;
21+
22+
import org.springframework.graphql.data.method.annotation.GraphQlExceptionHandler;
23+
import org.springframework.graphql.execution.ErrorType;
24+
import org.springframework.validation.BindException;
25+
import org.springframework.web.bind.annotation.ControllerAdvice;
26+
27+
@ControllerAdvice
28+
public class GlobalExceptionHandler {
29+
30+
@GraphQlExceptionHandler
31+
public GraphQLError handle(GraphqlErrorBuilder<?> errorBuilder, BindException ex) {
32+
return errorBuilder
33+
.errorType(ErrorType.BAD_REQUEST)
34+
.message(ex.getMessage())
35+
.build();
36+
}
37+
38+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.util.Optional;
2121

2222
import graphql.GraphQLContext;
23+
import graphql.GraphqlErrorBuilder;
2324
import graphql.schema.DataFetchingEnvironment;
2425
import graphql.schema.DataFetchingFieldSelectionSet;
2526

@@ -45,6 +46,7 @@ public boolean supportsParameter(MethodParameter parameter) {
4546
Class<?> type = parameter.getParameterType();
4647
return (type.equals(DataFetchingEnvironment.class) || type.equals(GraphQLContext.class) ||
4748
type.equals(DataFetchingFieldSelectionSet.class) ||
49+
type.equals(GraphqlErrorBuilder.class) ||
4850
type.equals(Locale.class) || isOptionalLocale(parameter));
4951
}
5052

@@ -70,6 +72,9 @@ else if (isOptionalLocale(parameter)) {
7072
else if (type.equals(DataFetchingEnvironment.class)) {
7173
return environment;
7274
}
75+
else if (type.equals(GraphqlErrorBuilder.class)) {
76+
return GraphqlErrorBuilder.newError(environment);
77+
}
7378
else {
7479
throw new IllegalStateException("Unexpected method parameter type: " + parameter);
7580
}
Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,9 +21,17 @@
2121
import java.util.Optional;
2222

2323
import graphql.GraphQLContext;
24+
import graphql.GraphQLError;
25+
import graphql.GraphqlErrorBuilder;
26+
import graphql.execution.ExecutionStepInfo;
27+
import graphql.execution.MergedField;
28+
import graphql.execution.ResultPath;
29+
import graphql.language.Field;
30+
import graphql.language.SourceLocation;
2431
import graphql.schema.DataFetchingEnvironment;
2532
import graphql.schema.DataFetchingEnvironmentImpl;
2633
import graphql.schema.DataFetchingFieldSelectionSet;
34+
import graphql.schema.GraphQLObjectType;
2735
import org.junit.jupiter.api.Test;
2836

2937
import org.springframework.core.MethodParameter;
@@ -35,11 +43,12 @@
3543
/**
3644
* Unit tests for {@link DataFetchingEnvironmentMethodArgumentResolver}.
3745
* @author Rossen Stoyanchev
46+
* @author Brian Clozel
3847
*/
39-
class DataFetchingEnvironmentArgumentResolverTests {
48+
class DataFetchingEnvironmentMethodArgumentResolverTests {
4049

4150
private static final Method handleMethod = ClassUtils.getMethod(
42-
DataFetchingEnvironmentArgumentResolverTests.class, "handle", (Class<?>[]) null);
51+
DataFetchingEnvironmentMethodArgumentResolverTests.class, "handle", (Class<?>[]) null);
4352

4453

4554
private final DataFetchingEnvironmentMethodArgumentResolver resolver =
@@ -51,8 +60,9 @@ void supportsParameter() {
5160
assertThat(this.resolver.supportsParameter(parameter(1))).isTrue();
5261
assertThat(this.resolver.supportsParameter(parameter(2))).isTrue();
5362
assertThat(this.resolver.supportsParameter(parameter(3))).isTrue();
63+
assertThat(this.resolver.supportsParameter(parameter(4))).isTrue();
5464

55-
assertThat(this.resolver.supportsParameter(parameter(4))).isFalse();
65+
assertThat(this.resolver.supportsParameter(parameter(5))).isFalse();
5666
}
5767

5868
@Test
@@ -94,6 +104,25 @@ void resolveOptionalLocale() {
94104
assertThat(actual.get()).isSameAs(locale);
95105
}
96106

107+
@Test
108+
void resolveErrorBuilder() {
109+
MergedField field = MergedField.newMergedField(Field.newField("greeting")
110+
.sourceLocation(SourceLocation.EMPTY).build()).build();
111+
ExecutionStepInfo executionStepInfo = ExecutionStepInfo.newExecutionStepInfo()
112+
.path(ResultPath.parse("/greeting"))
113+
.type(new GraphQLObjectType.Builder().name("project").build()).build();
114+
DataFetchingEnvironment environment = environment()
115+
.mergedField(field)
116+
.executionStepInfo(executionStepInfo)
117+
.build();
118+
119+
GraphqlErrorBuilder<?> errorBuilder = (GraphqlErrorBuilder<?>) this.resolver.resolveArgument(parameter(4), environment);
120+
GraphQLError error = errorBuilder.message("custom error message").build();
121+
122+
assertThat(ResultPath.fromList(error.getPath()).toString()).isEqualTo("/greeting");
123+
assertThat(error.getLocations()).isNotEmpty();
124+
}
125+
97126
private static DataFetchingEnvironmentImpl.Builder environment() {
98127
return DataFetchingEnvironmentImpl.newDataFetchingEnvironment();
99128
}
@@ -109,6 +138,7 @@ public void handle(
109138
DataFetchingFieldSelectionSet selectionSet,
110139
Locale locale,
111140
Optional<Locale> optionalLocale,
141+
GraphqlErrorBuilder<?> errorBuilder,
112142
String s) {
113143
}
114144

spring-graphql/src/test/java/org/springframework/graphql/execution/ExceptionResolversExceptionHandlerTests.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -64,6 +64,7 @@ void resolveException() throws Exception {
6464
assertThat(response.errorCount()).isEqualTo(1);
6565
assertThat(response.error(0).message()).isEqualTo("Resolved error: Invalid greeting");
6666
assertThat(response.error(0).errorType()).isEqualTo("BAD_REQUEST");
67+
assertThat(response.error(0).path()).isEqualTo("/greeting");
6768

6869
String greeting = response.rawValue("greeting");
6970
assertThat(greeting).isNull();
@@ -85,6 +86,7 @@ void resolveExceptionWithReactorContext() throws Exception {
8586
ResponseHelper response = ResponseHelper.forResult(result);
8687
assertThat(response.errorCount()).isEqualTo(1);
8788
assertThat(response.error(0).message()).isEqualTo("Resolved error: Invalid greeting, name=007");
89+
assertThat(response.error(0).path()).isEqualTo("/greeting");
8890
}
8991

9092
@Test
@@ -110,6 +112,7 @@ void resolveExceptionWithThreadLocal() {
110112
ResponseHelper response = ResponseHelper.forResult(result);
111113
assertThat(response.errorCount()).isEqualTo(1);
112114
assertThat(response.error(0).message()).isEqualTo("Resolved error: Invalid greeting, name=007");
115+
assertThat(response.error(0).path()).isEqualTo("/greeting");
113116
}
114117
finally {
115118
threadLocal.remove();
@@ -127,6 +130,7 @@ void unresolvedException() throws Exception {
127130
ResponseHelper response = ResponseHelper.forResult(result);
128131
assertThat(response.errorCount()).isEqualTo(1);
129132
assertThat(response.error(0).message()).startsWith("INTERNAL_ERROR for ");
133+
assertThat(response.error(0).path()).isEqualTo("/greeting");
130134
assertThat(response.error(0).errorType()).isEqualTo("INTERNAL_ERROR");
131135

132136
String greeting = response.rawValue("greeting");

0 commit comments

Comments
 (0)