Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit 293684d

Browse files
committed
Support conversion of one exception to collection of GraphQLError
1 parent 4f1da7d commit 293684d

File tree

8 files changed

+204
-117
lines changed

8 files changed

+204
-117
lines changed

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1818
#
1919

20-
version = 5.10.1-SNAPSHO
20+
version = 5.11.0-SNAPSHOT
2121

2222
PROJECT_GROUP = com.graphql-java-kickstart
2323
PROJECT_NAME = graphql-spring-boot
@@ -42,7 +42,7 @@ LIB_JUNIT_VER = 4.12
4242
LIB_SPRING_CORE_VER = 5.0.4.RELEASE
4343
LIB_SPRING_BOOT_VER = 2.1.6.RELEASE
4444
LIB_GRAPHQL_SERVLET_VER = 8.0.0
45-
LIB_GRAPHQL_JAVA_TOOLS_VER = 5.6.1
45+
LIB_GRAPHQL_JAVA_TOOLS_VER = 5.7.0
4646
LIB_COMMONS_IO_VER = 2.6
4747
kotlin.version=1.3.31
4848

graphql-spring-boot-autoconfigure/src/main/java/com/oembedler/moon/graphql/boot/error/GraphQLErrorFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
import graphql.GraphQLError;
44

55
import java.lang.reflect.Method;
6+
import java.util.Collection;
67
import java.util.Optional;
78

89
interface GraphQLErrorFactory {
910

1011
Optional<Class<? extends Throwable>> mostConcrete(Throwable t);
1112

12-
GraphQLError create(Throwable t);
13+
Collection<GraphQLError> create(Throwable t);
1314

1415
static GraphQLErrorFactory withReflection(Object object, Method method) {
1516
return new ReflectiveGraphQLErrorFactory(object, method);
Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,60 @@
11
package com.oembedler.moon.graphql.boot.error;
22

3+
import static java.util.Collections.singletonList;
4+
35
import graphql.ExceptionWhileDataFetching;
46
import graphql.GraphQLError;
57
import graphql.GraphQLException;
68
import graphql.SerializationError;
79
import graphql.servlet.core.DefaultGraphQLErrorHandler;
810
import graphql.servlet.core.GenericGraphQLError;
9-
import lombok.extern.slf4j.Slf4j;
10-
11+
import java.util.Collection;
1112
import java.util.HashMap;
1213
import java.util.List;
1314
import java.util.Map;
1415
import java.util.Optional;
1516
import java.util.stream.Collectors;
17+
import lombok.Getter;
18+
import lombok.extern.slf4j.Slf4j;
1619

1720
@Slf4j
21+
@Getter
1822
class GraphQLErrorFromExceptionHandler extends DefaultGraphQLErrorHandler {
1923

20-
private List<GraphQLErrorFactory> factories;
24+
private List<GraphQLErrorFactory> factories;
2125

22-
GraphQLErrorFromExceptionHandler(List<GraphQLErrorFactory> factories) {
23-
this.factories = factories;
24-
}
26+
GraphQLErrorFromExceptionHandler(List<GraphQLErrorFactory> factories) {
27+
this.factories = factories;
28+
}
2529

26-
protected List<GraphQLError> filterGraphQLErrors(List<GraphQLError> errors) {
27-
return errors.stream().map(this::transform).collect(Collectors.toList());
28-
}
30+
protected List<GraphQLError> filterGraphQLErrors(List<GraphQLError> errors) {
31+
return errors.stream().map(this::transform).flatMap(Collection::stream).collect(Collectors.toList());
32+
}
2933

30-
private GraphQLError transform(GraphQLError error) {
31-
return extractException(error).map(this::transform).orElse(defaultError(error.getMessage()));
32-
}
33-
34-
private Optional<Throwable> extractException(GraphQLError error) {
35-
if (error instanceof ExceptionWhileDataFetching) {
36-
return Optional.of(((ExceptionWhileDataFetching) error).getException());
37-
} else if (error instanceof SerializationError) {
38-
return Optional.of(((SerializationError) error).getException());
39-
} else if (error instanceof GraphQLException) {
40-
return Optional.of((GraphQLException) error);
41-
}
42-
return Optional.empty();
43-
}
44-
45-
private GraphQLError transform(Throwable throwable) {
46-
Map<Class<? extends Throwable>, GraphQLErrorFactory> applicables = new HashMap<>();
47-
factories.forEach(factory -> factory.mostConcrete(throwable).ifPresent(t -> applicables.put(t, factory)));
48-
return applicables.keySet().stream()
49-
.min(new ThrowableComparator())
50-
.map(applicables::get)
51-
.map(factory -> factory.create(throwable))
52-
.orElse(this.defaultError(throwable));
53-
}
54-
55-
private GraphQLError defaultError(Throwable throwable) {
56-
return new ThrowableGraphQLError(throwable);
57-
}
34+
private Collection<GraphQLError> transform(GraphQLError error) {
35+
return extractException(error).map(this::transform)
36+
.orElse(singletonList(new GenericGraphQLError(error.getMessage())));
37+
}
5838

59-
private GraphQLError defaultError(String message) {
60-
return new GenericGraphQLError(message);
39+
private Optional<Throwable> extractException(GraphQLError error) {
40+
if (error instanceof ExceptionWhileDataFetching) {
41+
return Optional.of(((ExceptionWhileDataFetching) error).getException());
42+
} else if (error instanceof SerializationError) {
43+
return Optional.of(((SerializationError) error).getException());
44+
} else if (error instanceof GraphQLException) {
45+
return Optional.of((GraphQLException) error);
6146
}
47+
return Optional.empty();
48+
}
49+
50+
private Collection<GraphQLError> transform(Throwable throwable) {
51+
Map<Class<? extends Throwable>, GraphQLErrorFactory> applicables = new HashMap<>();
52+
factories.forEach(factory -> factory.mostConcrete(throwable).ifPresent(t -> applicables.put(t, factory)));
53+
return applicables.keySet().stream()
54+
.min(new ThrowableComparator())
55+
.map(applicables::get)
56+
.map(factory -> factory.create(throwable))
57+
.orElse(singletonList(new ThrowableGraphQLError(throwable)));
58+
}
6259

6360
}
Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,57 @@
11
package com.oembedler.moon.graphql.boot.error;
22

3-
import graphql.GraphQLError;
3+
import static com.oembedler.moon.graphql.boot.error.GraphQLErrorFactory.withReflection;
4+
import static java.util.Collections.emptyList;
5+
import static java.util.stream.Collectors.toList;
6+
47
import graphql.servlet.core.DefaultGraphQLErrorHandler;
58
import graphql.servlet.core.GraphQLErrorHandler;
9+
import java.util.Arrays;
10+
import java.util.List;
11+
import java.util.Objects;
612
import lombok.extern.slf4j.Slf4j;
713
import org.springframework.beans.factory.BeanCreationException;
814
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
915
import org.springframework.context.ApplicationContext;
1016
import org.springframework.context.ConfigurableApplicationContext;
1117
import org.springframework.web.bind.annotation.ExceptionHandler;
1218

13-
import java.lang.reflect.Method;
14-
import java.util.Arrays;
15-
import java.util.Collections;
16-
import java.util.List;
17-
import java.util.stream.Collectors;
18-
1919
@Slf4j
2020
public class GraphQLErrorHandlerFactory {
2121

22-
public GraphQLErrorHandler create(ConfigurableApplicationContext applicationContext, boolean exceptionHandlersEnabled) {
23-
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
24-
List<GraphQLErrorFactory> factories = Arrays.stream(beanFactory.getBeanDefinitionNames())
25-
.filter(applicationContext::containsBean)
26-
.map(name -> scanForExceptionHandlers(applicationContext, name))
27-
.flatMap(List::stream)
28-
.collect(Collectors.toList());
29-
30-
if (!factories.isEmpty() || exceptionHandlersEnabled) {
31-
log.debug("Handle GraphQL errors using exception handlers defined in {} custom factories", factories.size());
32-
return new GraphQLErrorFromExceptionHandler(factories);
33-
}
34-
35-
log.debug("Using default GraphQL error handler");
36-
return new DefaultGraphQLErrorHandler();
37-
}
38-
39-
private List<GraphQLErrorFactory> scanForExceptionHandlers(ApplicationContext context, String name) {
40-
try {
41-
Class<?> objClz = context.getType(name);
42-
if (objClz == null) {
43-
log.info("Cannot load class " + name);
44-
return Collections.emptyList();
45-
}
46-
return Arrays.stream(objClz.getDeclaredMethods())
47-
.filter(this::isGraphQLExceptionHandlerMethod)
48-
.map(method -> GraphQLErrorFactory.withReflection(context.getBean(name), method))
49-
.collect(Collectors.toList());
50-
} catch (BeanCreationException e) {
51-
log.error("Cannot load class " + name + ". " + e.getMessage());
52-
return Collections.emptyList();
53-
}
22+
public GraphQLErrorHandler create(ConfigurableApplicationContext applicationContext,
23+
boolean exceptionHandlersEnabled) {
24+
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
25+
List<GraphQLErrorFactory> factories = Arrays.stream(beanFactory.getBeanDefinitionNames())
26+
.filter(applicationContext::containsBean)
27+
.map(name -> scanForExceptionHandlers(applicationContext, name))
28+
.flatMap(List::stream)
29+
.collect(toList());
30+
31+
if (!factories.isEmpty() || exceptionHandlersEnabled) {
32+
log.debug("Handle GraphQL errors using exception handlers defined in {} custom factories", factories.size());
33+
return new GraphQLErrorFromExceptionHandler(factories);
5434
}
5535

56-
private boolean isGraphQLExceptionHandlerMethod(Method method) {
57-
return method.isAnnotationPresent(ExceptionHandler.class) && GraphQLError.class.isAssignableFrom(method.getReturnType());
36+
log.debug("Using default GraphQL error handler");
37+
return new DefaultGraphQLErrorHandler();
38+
}
39+
40+
private List<GraphQLErrorFactory> scanForExceptionHandlers(ApplicationContext context, String name) {
41+
try {
42+
Class<?> objClz = context.getType(name);
43+
if (objClz == null) {
44+
log.info("Cannot load class " + name);
45+
return emptyList();
46+
}
47+
return Arrays.stream(objClz.getDeclaredMethods())
48+
.filter(ReflectiveMethodValidator::isGraphQLExceptionHandler)
49+
.map(method -> withReflection(context.getBean(name), method))
50+
.collect(toList());
51+
} catch (BeanCreationException e) {
52+
log.error("Cannot load class {}. {}", name, e.getMessage());
53+
return emptyList();
5854
}
55+
}
5956

6057
}
Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,49 @@
11
package com.oembedler.moon.graphql.boot.error;
22

3+
import static java.util.Collections.singletonList;
4+
35
import graphql.GraphQLError;
46
import graphql.servlet.core.GenericGraphQLError;
5-
import lombok.extern.slf4j.Slf4j;
6-
import org.springframework.web.bind.annotation.ExceptionHandler;
7-
87
import java.lang.reflect.InvocationTargetException;
98
import java.lang.reflect.Method;
9+
import java.util.Collection;
1010
import java.util.Optional;
11+
import lombok.extern.slf4j.Slf4j;
12+
import org.springframework.web.bind.annotation.ExceptionHandler;
1113

1214
@Slf4j
1315
class ReflectiveGraphQLErrorFactory implements GraphQLErrorFactory {
1416

15-
private Object object;
16-
private Method method;
17-
private Throwables throwables;
18-
19-
ReflectiveGraphQLErrorFactory(Object object, Method method) {
20-
this.object = object;
21-
this.method = method;
22-
23-
throwables = new Throwables(method.getAnnotation(ExceptionHandler.class).value());
24-
}
25-
26-
@Override
27-
public Optional<Class<? extends Throwable>> mostConcrete(Throwable t) {
28-
return throwables.mostConcrete(t);
29-
}
30-
31-
@Override
32-
public GraphQLError create(Throwable t) {
33-
try {
34-
method.setAccessible(true);
35-
return (GraphQLError) method.invoke(object, t);
36-
} catch (IllegalAccessException | InvocationTargetException e) {
37-
log.error("Cannot create GraphQLError from throwable {}", t.getClass().getSimpleName(), e);
38-
return new GenericGraphQLError(t.getMessage());
39-
}
17+
private final boolean singularReturnType;
18+
private Object object;
19+
private Method method;
20+
private Throwables throwables;
21+
22+
ReflectiveGraphQLErrorFactory(Object object, Method method) {
23+
this.object = object;
24+
this.method = method;
25+
singularReturnType = GraphQLError.class.isAssignableFrom(method.getReturnType());
26+
27+
throwables = new Throwables(method.getAnnotation(ExceptionHandler.class).value());
28+
}
29+
30+
@Override
31+
public Optional<Class<? extends Throwable>> mostConcrete(Throwable t) {
32+
return throwables.mostConcrete(t);
33+
}
34+
35+
@Override
36+
public Collection<GraphQLError> create(Throwable t) {
37+
try {
38+
method.setAccessible(true);
39+
if (singularReturnType) {
40+
return singletonList((GraphQLError) method.invoke(object, t));
41+
}
42+
return (Collection<GraphQLError>) method.invoke(object, t);
43+
} catch (IllegalAccessException | InvocationTargetException e) {
44+
log.error("Cannot create GraphQLError from throwable {}", t.getClass().getSimpleName(), e);
45+
return singletonList(new GenericGraphQLError(t.getMessage()));
4046
}
47+
}
4148

4249
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.oembedler.moon.graphql.boot.error;
2+
3+
import graphql.GraphQLError;
4+
import java.lang.reflect.Method;
5+
import java.lang.reflect.ParameterizedType;
6+
import java.util.Collection;
7+
import lombok.AccessLevel;
8+
import lombok.NoArgsConstructor;
9+
import org.springframework.web.bind.annotation.ExceptionHandler;
10+
11+
@NoArgsConstructor(access = AccessLevel.PRIVATE)
12+
class ReflectiveMethodValidator {
13+
14+
static boolean isGraphQLExceptionHandler(Method method) {
15+
return method.isAnnotationPresent(ExceptionHandler.class) && (
16+
isGraphQLErrorReturnType(method) || hasGraphQLErrorCollectionReturnType(method)
17+
);
18+
}
19+
20+
static private boolean isGraphQLErrorReturnType(Method method) {
21+
return GraphQLError.class.isAssignableFrom(method.getReturnType());
22+
}
23+
24+
static private boolean hasGraphQLErrorCollectionReturnType(Method method) {
25+
if (Collection.class.isAssignableFrom(method.getReturnType())) {
26+
ParameterizedType collectionType = (ParameterizedType) method.getGenericReturnType();
27+
if (collectionType.getActualTypeArguments().length == 1) {
28+
return GraphQLError.class.isAssignableFrom((Class<?>) collectionType.getActualTypeArguments()[0]);
29+
}
30+
}
31+
return false;
32+
}
33+
34+
}

graphql-spring-boot-autoconfigure/src/test/java/com/oembedler/moon/graphql/boot/GraphQLErrorHandlerTest.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import com.oembedler.moon.graphql.boot.test.AbstractAutoConfigurationTest;
88
import graphql.GraphQL;
99
import graphql.GraphQLError;
10-
import graphql.schema.GraphQLObjectType;
1110
import graphql.schema.GraphQLSchema;
1211
import graphql.servlet.core.GraphQLErrorHandler;
1312
import graphql.servlet.core.GraphQLObjectMapper;
@@ -18,11 +17,6 @@
1817
import org.springframework.web.bind.annotation.ExceptionHandler;
1918
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
2019

21-
import java.io.IOException;
22-
import java.util.List;
23-
24-
import static org.junit.Assert.*;
25-
2620
public class GraphQLErrorHandlerTest extends AbstractAutoConfigurationTest {
2721

2822
private GraphQL gql;

0 commit comments

Comments
 (0)