サーブレットとサービスのどちらで発生するかに関係なく、jsonでカスタムエラーオブジェクト/メッセージを送信する必要があるgraphqlアプリケーションで作業しています。
予期されるエラー応答
{ errorCode: 400 //error goes here, errorMessage: "my error mesage"}
誰かが上記の要件を達成するために私を導くことができればそれは役に立ちます。
GraphQL仕様 応答のerror
エントリの明確な形式を定義します。
仕様によると、次のようになります(JSON形式が使用されていると仮定):
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ]
"extensions": {/* You can place data in any format here */}
}
]
したがって、それを拡張してGraphQLの実行結果に次のようなものを返すことができるGraphQL実装は見つかりません。次に例を示します。
"errors": [
{
"errorMessage": "Name for character with ID 1002 could not be fetched.",
"errorCode": 404
}
]
ただし、この仕様では、extension
エントリに任意の形式でデータを追加できます。したがって、サーバー側でカスタム例外を作成し、JSONで次のような応答を返すことができます。
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ]
"extensions": {
"errorMessage": "Name for character with ID 1002 could not be fetched.",
"errorCode": 404
}
}
]
docs で説明されているように、これをGraphQLJavaに実装するのは非常に簡単です。 getExtensions
メソッドをオーバーライドするカスタム例外を作成し、実装内にマップを作成して、それを使用してextensions
のコンテンツを作成できます。
public class CustomException extends RuntimeException implements GraphQLError {
private final int errorCode;
public CustomException(int errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
}
@Override
public Map<String, Object> getExtensions() {
Map<String, Object> customAttributes = new LinkedHashMap<>();
customAttributes.put("errorCode", this.errorCode);
customAttributes.put("errorMessage", this.getMessage());
return customAttributes;
}
@Override
public List<SourceLocation> getLocations() {
return null;
}
@Override
public ErrorType getErrorType() {
return null;
}
}
次に、データフェッチャー内からコードとメッセージを渡す例外をスローできます。
throw new CustomException(400, "A custom error message");
さて、これに取り組む別の方法があります。
Webアプリケーションで作業していると仮定すると、は任意の形式でエラー(およびさらに言えばデータ)を返すことができます。それは私の意見では少し厄介ですが。 ApolloのようなGraphQLクライアントは仕様に準拠しているのに、なぜ他の形式で応答を返したいのでしょうか。しかしとにかく、そこにはさまざまな要件がたくさんあります。
ExecutionResult
を入手したら、任意の形式でマップまたはオブジェクトを作成し、それをJSONとしてシリアル化し、これをHTTP経由で返すことができます。
Map<String, Object> result = new HashMap<>();
result.put("data", executionResult.getData());
List<Map<String, Object>> errors = executionResult.getErrors()
.stream()
.map(error -> {
Map<String, Object> errorMap = new HashMap<>();
errorMap.put("errorMessage", error.getMessage());
errorMap.put("errorCode", 404); // get the code somehow from the error object
return errorMap;
})
.collect(toList());
result.put("errors", errors);
// Serialize "result" and return that.
しかし、繰り返しになりますが、仕様に準拠していない応答があることは、ほとんどの場合意味がありません。
他の投稿された回答は私にはうまくいきませんでした。次のクラスを作成して解決策を見つけました。
1)CustomException
タイプのスロー可能なGraphQLError
(別の回答で言及されているように)。
2)GraphQLError
ではないThrowable
アダプターを作成します。
3)カスタム例外をフィルタリングするためのカスタムGraphQLErrorHandler
。
ステップ1:
以下のスロー可能なCustomGraphQLException
はGraphQLError
を実装します。これは、GraphQLErrorHandler
インターフェースがタイプGraphQLError
のエラーのみを受け入れるためです。
public class CustomGraphQLException extends RuntimeException implements GraphQLError {
private final int errorCode;
private final String errorMessage;
public CustomGraphQLException(int errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
@Override
public List<SourceLocation> getLocations() {
return null;
}
@Override
public ErrorType getErrorType() {
return null;
}
@Override
public String getMessage() {
return this.errorMessage;
}
@Override
public Map<String, Object> getExtensions() {
Map<String, Object> customAttributes = new HashMap<>();
customAttributes.put("errorCode", this.errorCode);
customAttributes.put("errorMessage", this.getMessage());
return customAttributes;
}
}
ステップ2:GraphQLError
のスロー不可能なアダプターが作成され、上記のカスタム例外のスタックトレースが最終的なGraphQLエラー応答で渡されるのを回避します。
public class GraphQLErrorAdaptor implements GraphQLError {
private final GraphQLError graphQLError;
public GraphQLErrorAdaptor(GraphQLError graphQLError) {
this.graphQLError = graphQLError;
}
@Override
public List<SourceLocation> getLocations() {
return graphQLError.getLocations();
}
@Override
public ErrorType getErrorType() {
return graphQLError.getErrorType();
}
@Override
public String getMessage() {
return graphQLError.getMessage();
}
@Override
public Map<String, Object> getExtensions() {
return graphQLError.getExtensions();
}
}
ステップ3:
カスタムGraphQLErrorHandler
を実装して、カスタムCustomGraphQLException
をフィルタリングし、デフォルトのgraphQLエラー応答への置き換えを回避します。
public class CustomGraphQLErrorHandler implements GraphQLErrorHandler {
public CustomGraphQLErrorHandler() { }
public List<GraphQLError> processErrors(List<GraphQLError> errors) {
List<GraphQLError> clientErrors = this.filterGraphQLErrors(errors);
List<GraphQLError> internalErrors = errors.stream()
.filter(e -> isInternalError(e))
.map(GraphQLErrorAdaptor::new)
.collect(Collectors.toList());
if (clientErrors.size() + internalErrors.size() < errors.size()) {
clientErrors.add(new GenericGraphQLError("Internal Server Error(s) while executing query"));
errors.stream().filter((error) -> !this.isClientError(error)
).forEach((error) -> {
if (error instanceof Throwable) {
LOG.error("Error executing query!", (Throwable) error);
} else {
LOG.error("Error executing query ({}): {}", error.getClass().getSimpleName(), error.getMessage());
}
});
}
List<GraphQLError> finalErrors = new ArrayList<>();
finalErrors.addAll(clientErrors);
finalErrors.addAll(internalErrors);
return finalErrors;
}
protected List<GraphQLError> filterGraphQLErrors(List<GraphQLError> errors) {
return errors.stream().filter(this::isClientError).collect(Collectors.toList());
}
protected boolean isClientError(GraphQLError error) {
return !(error instanceof ExceptionWhileDataFetching) && !(error instanceof Throwable);
}
protected boolean isInternalError(GraphQLError error) {
return (error instanceof ExceptionWhileDataFetching) &&
(((ExceptionWhileDataFetching) error).getException() instanceof CustomGraphQLException);
}
}
ステップ4:CustomGraphQLErrorHandler
にGraphQLServlet
を設定します。このステップではspring-boot
を使用していると想定しています。
@Configuration
public class GraphQLConfig {
@Bean
public ServletRegistrationBean graphQLServletRegistrationBean(
QueryResolver queryResolver,
CustomGraphQLErrorHandler customGraphQLErrorHandler) throws Exception {
GraphQLSchema schema = SchemaParser.newParser()
.schemaString(IOUtils.resourceToString("/library.graphqls", Charset.forName("UTF-8")))
.resolvers(queryResolver)
.build()
.makeExecutableSchema();
return new ServletRegistrationBean(new SimpleGraphQLServlet(schema,
new DefaultExecutionStrategyProvider(), null, null, null,
customGraphQLErrorHandler, new DefaultGraphQLContextBuilder(), null,
null), "/graphql");
}
}