GraphQL Java以json格式发送自定义错误

时间:2018-08-29 18:24:11

标签: json error-handling graphql graphql-java

我正在graphgraphl应用程序中工作,无论它是否出现在servlet或服务中,我都必须在json中发送自定义错误对象/消息。

预期的错误响应

plot "< awk <data.asc 'end!=0 && NR<=end{print} /^start/{end=NR+1500}'" using 1:2 with lines

如果有人可以指导我达到上述要求,将会很有帮助。

2 个答案:

答案 0 :(得分:5)

GraphQL specification为响应中的error条目定义了清晰的格式。

根据规范,它应该像这样(假设使用JSON格式):

  "errors": [
    {
      "message": "Name for character with ID 1002 could not be fetched.",
      "locations": [ { "line": 6, "column": 7 } ],
      "path": [ "hero", "heroFriends", 1, "name" ]
      "extension": {/* 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条目中以任何格式添加数据。因此,您可以在服务器端创建一个自定义Exception并最终得到一个类似于JSON的响应:

  "errors": [
    {
      "message": "Name for character with ID 1002 could not be fetched.",
      "locations": [ { "line": 6, "column": 7 } ],
      "path": [ "hero", "heroFriends", 1, "name" ]
      "extension": {
          "errorMessage": "Name for character with ID 1002 could not be fetched.",
          "errorCode": 404
      }
    }
  ]

很容易在GraphQL Java上实现它,如the docs中所述。您可以创建一个覆盖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应用程序,则可以以所需的任何格式返回错误(以及相关数据)。虽然在我看来这有点尴尬。 GraphQL客户端(如Apollo)遵守该规范,那么为什么要返回任何其他格式的响应?但是无论如何,那里有很多不同的要求。

一旦拥有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 :(得分:2)

另一个发布的答案对我不起作用。 我通过创建以下类找到了解决方案:

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");

    }

}

Reference