【问题标题】:How to handle GraphQL query validation error in Spring Boot如何处理 Spring Boot 中的 GraphQL 查询验证错误
【发布时间】:2022-06-30 08:50:22
【问题描述】:

我有一个使用 spring-boot-starter-graphql 的简单 Spring Boot 项目。这个项目有一个控制器,它接受一个参数。

@Controller
public class HelloNameController {

    @QueryMapping
    public String hello(@Argument String name) {
        return "Hello " + name;
    }
}

此参数是必需的。

Graphql 架构

 type Query {
     hello (name : String!) : String
  }

当我在 Postman 中调用此 API 且未传递此参数时,应用程序会返回错误。我想覆盖此错误消息的消息,但找不到方法。 在官方documentation 中,它说要实现DataFetcherExceptionResolverAdapter,我已经将它实现为一个bean

@Configuration
public class GraphQLConfig {

    @Bean
    public DataFetcherExceptionResolver exceptionResolver() {
        return DataFetcherExceptionResolverAdapter.from((ex, env) -> {
            if (ex instanceof CoercingParseValueException) {

                return GraphqlErrorBuilder.newError(env).message("CoercingParseValueException")
                        .errorType(ErrorType.ExecutionAborted).build();
            }
            if (ex instanceof CoercingSerializeException) {
                return GraphqlErrorBuilder.newError(env).message("CoercingSerializeException")
                        .errorType(ErrorType.ExecutionAborted).build();
            } else {
                return null;
            }
        });
    }
}

问题是错误永远不会到达这一点。如何捕获此类错误并覆盖该消息?

【问题讨论】:

    标签: java spring spring-boot graphql graphql-java


    【解决方案1】:

    我在 GitHub 上问过类似的问题。 graphql-java 项目 (#2866) 和 spring-graphql 项目 (#415) 的响应相似。在撰写本文时进行总结是不可能的。 然后我创建了一个“解决方法”:

    首先,创建一个实现 GraphQLError 的自定义异常类。

    import graphql.GraphQLError;
    import graphql.language.SourceLocation;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import org.springframework.graphql.execution.ErrorType;
    import org.springframework.http.HttpStatus;
    
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    
    @Getter
    @NoArgsConstructor
    public class BadRequestException extends RuntimeException implements GraphQLError {
    
        private HttpStatus status = HttpStatus.BAD_REQUEST;
    
        private String message = "Resource not found";
    
        // Below code used for GraphQL only
        private List<SourceLocation> locations;
    
        public BadRequestException(String message, List<SourceLocation> locations) {
            this.message = message;
            this.locations = locations;
        }
    
        @Override
        public Map<String, Object> getExtensions() {
            Map<String, Object> customAttributes = new LinkedHashMap<>();
            customAttributes.put("errorCode", this.status.value());
            return customAttributes;
        }
    
        @Override
        public List<SourceLocation> getLocations() {
            return locations;
        }
    
        @Override
        public ErrorType getErrorType() {
            return ErrorType.BAD_REQUEST;
        }
    
        @Override
        public Map<String, Object> toSpecification() {
            return GraphQLError.super.toSpecification();
        }
    
    }
    

    其次,创建一个实现WebGraphQlInterceptor的拦截器类,并将其注解为@Component,这样Spring就可以将其创建为一个bean。在这个类内部实现逻辑来捕获所需的错误并将其转换为之前创建的异常类

    import graphql.ErrorClassification;
    import graphql.ErrorType;
    import graphql.GraphQLError;
    import graphql.validation.ValidationErrorType;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.graphql.ResponseError;
    import org.springframework.graphql.server.WebGraphQlInterceptor;
    import org.springframework.graphql.server.WebGraphQlRequest;
    import org.springframework.graphql.server.WebGraphQlResponse;
    import org.springframework.stereotype.Component;
    import reactor.core.publisher.Mono;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    @Slf4j
    @Component
    public class ErrorInterceptor implements WebGraphQlInterceptor {
    
        @Override
        public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
            return chain.next(request)
                    .map(response -> {
                        log.info("[ErrorInterceptor] Intercepting response... ");
    
                        List<GraphQLError> graphQLErrors = response.getErrors().stream()
                                .filter(error -> ErrorType.ValidationError.equals(error.getErrorType()))
                                .map(this::resolveException)
                                .collect(Collectors.toList());
    
                        if (!graphQLErrors.isEmpty()) {
                            log.info("[ErrorInterceptor] Found invalid syntax error! Overriding the message.");
                            return response.transform(builder -> builder.errors(graphQLErrors));
                        }
    
                        return response;
                    });
        }
    
        private GraphQLError resolveException(ResponseError responseError) {
    
            ErrorClassification errorType = responseError.getErrorType();
    
            if (ErrorType.ValidationError.equals(errorType)) {
                String message = responseError.getMessage();
                log.info("[ErrorInterceptor] Returning invalid field error ");
    
                if (ValidationErrorType.NullValueForNonNullArgument.equals(
                        extractValidationErrorFromErrorMessage(responseError.getMessage()))) {
                    String errorMessage =
                            "Field " + StringUtils.substringBetween(message, "argument ", " @") + " cannot be null";
                    return new BadRequestException(errorMessage, responseError.getLocations());
                }
            }
    
            log.info("[ErrorInterceptor] Returning unknown query validation error ");
            return new BadRequestException("Unknown error", responseError.getLocations());
        }
    
        private ValidationErrorType extractValidationErrorFromErrorMessage(String message) {
            return ValidationErrorType.valueOf(StringUtils.substringBetween(message, "type ", ":"));
        }
    
    }
    

    这种方法的唯一问题是所有需要的信息,如错误类型、导致错误的字段等,都嵌入在本机错误消息中。因此,要提取所需的参数,我们必须解析字符串消息。

    【讨论】:

      猜你喜欢
      • 2019-11-17
      • 2020-09-16
      • 2018-02-23
      • 1970-01-01
      • 2020-05-28
      • 2020-06-03
      • 2016-04-16
      • 1970-01-01
      • 2018-08-29
      相关资源
      最近更新 更多