如何处理Spring Boot中的GraphQl查询验证错误

发布于 2025-02-06 03:53:56 字数 1483 浏览 1 评论 0 原文

我使用 spring-boot-starter-graphql 有一个简单的春季启动项目。该项目有一个接受一个参数的控制器。

@Controller
public class HelloNameController {

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

需要这个论点。

GraphQL模式

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

在邮递员中调用此API时,请勿通过此参数返回错误。我想覆盖此错误消息的消息,但我找不到方法。 在官方 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;
            }
        });
    }
}

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

I have a simple Spring Boot project using spring-boot-starter-graphql. This project has one controller that accepts one argument.

@Controller
public class HelloNameController {

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

This argument is required.

Graphql schema

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

When I call this API in the Postman and do not pass this argument the app returns an error. I want to override the message of this error message, but I can't find a way to do it.
In the official documentation, it says to implement DataFetcherExceptionResolverAdapter and I've implemented it as a 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;
            }
        });
    }
}

The problem is that the error never gets to this point. How do I catch this type of error and override the message?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

辞旧 2025-02-13 03:53:56

我在github上问了一个类似的问题。来自GraphQL-JAVA项目的响应(#2866 )项目(#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的interceptor类,因此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 ", ":"));
    }

}

这是所有需要的信息(例如一种错误类型),导致错误的字段等。本机错误消息。因此,要提取所需的参数,我们必须解析字符串消息。

I've asked a similar question on a GitHub. Responses from graphql-java project (#2866) and spring-graphql project (#415) were similar. To summarise at the time of writing it is not possible.
Then I've created a "workaround":

First, create a custom exception class that implements 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();
    }

}

Second, create an interceptor class that implements WebGraphQlInterceptor and annotate it as @Component, so Spring can create it as a bean. Inside this class implement logic to catch the needed error and convert it to the exception class created before

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 ", ":"));
    }

}

The only problem with this approach is that all needed information like a type of an error, the field that causes the error, etc. is embedded in the native error message. So to extract the needed parameters we have to parse the string message.

走走停停 2025-02-13 03:53:56

这是Kotlin中的完整正确错误处理,包括验证验证。

这些错误是由 graphql.execute()抛出/v22/异常“ rel =“ nofollow noreferrer”>在这里

spring 此处

enum class MyErrorType(val description: String) : ErrorClassification {
    STUB_TYPE("This is a stub type");

    fun create(message: String? = null, cause: Throwable? = null) = MyException(this, message, cause)
}

@GraphQlExceptionHandler // create this function for every concrete exception that occurred during fetching and then Throwable as the last fallback. Or create this one and has a switch case on exception type. Or provide a DataFetcherExceptionResolverAdapter bean.
fun handle(ex: Throwable, env: DataFetchingEnvironment): GraphQLError {
  val errorBuilder = GraphqlErrorBuilder.newError(env)
            .errorType(org.springframework.graphql.execution.ErrorType.INTERNAL_ERROR)
  ... // set some error type (your own or pick one from spring's ErrorType) here so that below if doesn't catch it again. You should set it either way as it's client-facing.
}

@GraphQlExceptionHandler // example for MyException
fun handleMyError(ex: MyException, env: DataFetchingEnvironment): GraphQLError {
  val errorBuilder = GraphqlErrorBuilder.newError(env)
         .errorType(ex.type) // ex.type would be the MyErrorType from above
  ...
}

@Bean // anything not handled by the above will be caught here
fun requestErrorHandler() = WebGraphQlInterceptor { request: WebGraphQlRequest,
                                                    chain: WebGraphQlInterceptor.Chain
    ->
    chain.next(request).map { response ->
        if (response.isValid) return@map response
        val errors = response.errors.stream()
            .map { error ->
                // https://www.graphql-java.com/documentation/v22/exceptions
                if (error.errorType is graphql.ErrorType) log.error(
                    "Unexpected ${error.errorType} error in '${request.operationName}': $error\n${request.document}, vars: ${request.variables}, id: ${request.id}") // I would print vars as JSON for clarity through
                GraphqlErrorBuilder.newError()
                    .errorType(ErrorType.INTERNAL_ERROR)
                    .message("internal error")
                    .build()
            }
            .toList()
        response.transform { builder -> builder.errors(errors).build() }
    }
}

Here's the full proper error handling in kotlin including the validation ones.

Those errors are thrown by GraphQL.execute() don't count as "data fetching" errors - documented by GraphQL Java here

The interceptor is documented by Spring here

enum class MyErrorType(val description: String) : ErrorClassification {
    STUB_TYPE("This is a stub type");

    fun create(message: String? = null, cause: Throwable? = null) = MyException(this, message, cause)
}

@GraphQlExceptionHandler // create this function for every concrete exception that occurred during fetching and then Throwable as the last fallback. Or create this one and has a switch case on exception type. Or provide a DataFetcherExceptionResolverAdapter bean.
fun handle(ex: Throwable, env: DataFetchingEnvironment): GraphQLError {
  val errorBuilder = GraphqlErrorBuilder.newError(env)
            .errorType(org.springframework.graphql.execution.ErrorType.INTERNAL_ERROR)
  ... // set some error type (your own or pick one from spring's ErrorType) here so that below if doesn't catch it again. You should set it either way as it's client-facing.
}

@GraphQlExceptionHandler // example for MyException
fun handleMyError(ex: MyException, env: DataFetchingEnvironment): GraphQLError {
  val errorBuilder = GraphqlErrorBuilder.newError(env)
         .errorType(ex.type) // ex.type would be the MyErrorType from above
  ...
}

@Bean // anything not handled by the above will be caught here
fun requestErrorHandler() = WebGraphQlInterceptor { request: WebGraphQlRequest,
                                                    chain: WebGraphQlInterceptor.Chain
    ->
    chain.next(request).map { response ->
        if (response.isValid) return@map response
        val errors = response.errors.stream()
            .map { error ->
                // https://www.graphql-java.com/documentation/v22/exceptions
                if (error.errorType is graphql.ErrorType) log.error(
                    "Unexpected ${error.errorType} error in '${request.operationName}': $error\n${request.document}, vars: ${request.variables}, id: ${request.id}") // I would print vars as JSON for clarity through
                GraphqlErrorBuilder.newError()
                    .errorType(ErrorType.INTERNAL_ERROR)
                    .message("internal error")
                    .build()
            }
            .toList()
        response.transform { builder -> builder.errors(errors).build() }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文