使用GraphQL(gqlgen),定义错误和错误代码化

首先

(Note: The requested option is provided in Simplified Chinese)

最近,我注意到GraphQL这个词越来越常听到。我认为在新的开发项目中,很多人都选择使用GraphQL作为服务。本文将介绍在GraphQL服务器中引入错误代码概念时,使用gqlgen的简单实现方法。

這篇文章的內容

    • GraphQL でエラーコード周りどう実装するのか

 

    gqlgen で実装すると スキーマでエラーコードを実装して、SetErrorPresenter で整形する

在GraphQL中使用错误代码。

无论是GraphQL还是REST等各种形式的API,通常都会预先确定客户端和错误代码,并根据这些代码控制客户端的处理。具体而言,对于发生的错误,会设置特定的代码,并根据这些错误代码确定显示哪个消息或执行哪个跳转等操作。在Firebase中,定义了特定的错误代码,客户端可以根据这些代码判断出现了什么样的错误。

auth/claims-too-large: 传递给setCustomUserClaims()的声明有效负载超过最大允许大小(1,000字节)。
auth/email-already-exists: 提供的电子邮件地址已被现有用户使用。每个用户需要唯一的电子邮件地址。
auth/id-token-expired: 指定的Firebase ID令牌已过期。

    • 参考: Admin Authentication API エラー

https://firebase.google.com/docs/auth/admin/errors?hl=ja

通過为可能发生的错误代码添加代码,可以清楚地知道发生了什么错误,并使用户更容易采取下一步行动。
要在GraphQL中采用这种错误代码结构,我认为有两种方法(也可能有更多方法)。

    • エラーコードを全体共通のものとしてスキーマ上で定義する

 

    mutation や query などに応じてそれぞれの操作個別にエラー型をスキーマ上で定義する

无论是哪种方法,都需要生成一个称为错误代码的特定代码。
在GraphQL模式中定义错误类型,然后为每个错误定义一个名称。由于错误类型只有一个,所以如果该错误在多个位置发生,则需要生成不同的错误代码,即使错误内容相同。
例如,可以明显区分A服务的令牌过期和B服务的令牌过期。
尽管两者的过期时间都已到期,使用相同的错误代码可能是合适的,但由于服务不同,需要设置多个错误代码,就像(a/id-token-expired, b/id-token-expired)这样。(不一定非得这样做。)

如果以这种方式在GraphQL架构中定义错误代码,则如下所示。

type ErrorCode {
  serviceA: ErrorCodeServiceAEnum
  serviceB: ErrorCodeServiceBEnum
}

在上述例子中,定义了一个名为 ErrorCode 的错误码类型,并在其中定义了每个服务对应的错误码类型。通过这种方式,可以在模式上定义错误码。

使用 gqlgen 返回错误代码

在实际上服务器上进行实现时,我们应该如何去做呢?
因为我经常使用 Go,所以我想要使用一个叫做 gqlgen 的库来返回错误代码。

在 gqlgen 中,可以通过 SetErrorPresenter 来处理错误。
当调用 GraphQL 处理程序时,通过进行此设置并 return err,可以对在处理程序中检测到的错误进行格式化,以确定如何将其作为响应返回。

func (g *GraphQLHandler) GraphQL(c echo.Context) error {
    srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: g.Resolver}))
    //set error presenter
    srv.SetErrorPresenter(func(ctx context.Context, e error) *gqlerror.Error {
        // この辺で整形する処理を入れる
    })
    srv.ServeHTTP(c.Response(), c.Request())
*** 省略 ***
}

我通过上述代码示例进行了说明。
简单来说,在 srv.SetErrorPresenter 中格式化返回的错误可以让错误代码更好地返回。
具体的格式化处理,我认为可以使用 errors.Is 等进行格式化。

var ex map[string]interface{}
    if errors.Is(err, errUnauthorized) {
        ex = map[string]interface{}{
            "code":   model.ErrorCodeServiceAEnumUnauthorized, // gqlgen で自動生成されたエラー
            "status": http.StatusUnauthorized,
        }
    }
*** この辺に色々定義する *** 
    return &gqlerror.Error{
        Message:    err.Error(),
        Path:       graphql.GetPath(ctx),
        Locations:  nil,
        Extensions: ex,
        Rule:       "",
    }

本來我認為只會列出錯誤代碼 error.Is,但我只會舉一個例子。這是一個返回401錯誤的ServiceA的例子。
我認為使用擴展(extension)返回錯誤代碼和HTTP狀態碼是一種好方法。

我认为通过在扩展中添加代码,使得控制更加容易。

{
  "errors": [
    {
      "message": "hogehoge",
      "path": [
        "fugafuga"
      ],
      "extensions": {
        "code": "SERVICE_A_UNAUTHORIZED",
        "status": 401
      }
    }
  ],
  "data": null
}

最后

非常感谢您阅读到这里。

由于在GraphQL中仅通过响应状态码进行错误处理非常困难,所以我认为在此补充一个类似于错误代码机制的东西可能会更好。

我在这里介绍的内容只是简单的方法,我认为还有很多更好的方法,但如果能对您有所帮助,我会非常高兴。

广告
将在 10 秒后关闭
bannerAds