错误码管理
RK 推荐的 RPC 标准错误类型。
概述#
设计一个合理的 API 是一件不容易的事情,同时,API 还会产生各种不同的错误。
为了能让 API 使用者对于 API 的错误有一个清晰的视图,定义一个标准的 RPC 错误类型是非常重要的事情。
当使用 rk-boot 的时候,panic 中间件会默认加载到服务中,中间件会从 panic 中恢复,并且返回[内部错误]给用户。 默认使用 Google 样式的错误。
{
"error":{
"code":500,
"status":"Internal Server Error",
"message":"Panic occurs",
"details":[
"panic manually"
]
}
}
- Amazon 样式
{
"response":{
"errors":[
{
"error":{
"code":500,
"status":"Internal Server Error",
"message":"Panic occurs",
"details":[
"panic manually"
]
}
}
]
}
}
- 自定义样式
请参照后面的例子。
快速开始#
- 安装
$ go get github.com/rookie-ninja/rk-boot/v2
$ go get github.com/rookie-ninja/rk-grpc/v2
RPC 中返回错误#
本例子中,介绍如何返回错误给 API 使用者的[最佳实践]
func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
return nil, rkgrpcerr.Unimplemented("Trigger manually!", errors.New("this is detail")).Err()
}
```shell script $ curl localhost:8080/v1/hello { "error":{ "code":501, "status":"Not Implemented", "message":"Trigger manually!", "details":[ { "code":12, "status":"Unimplemented", "message":"[from-grpc] Trigger manually!" }, { "code":2, "status":"Unknown", "message":"this is detail" } ] } }
### Amazon 样式错误
- boot.yaml
```yaml
grpc:
- name: greeter
enabled: true
port: 8080
# gwPort: 8081 # 可选项,如果不指定,会使用与 port 一样的端口
enableRkGwOption: true
middleware:
errorModel: amazon
{
"response":{
"errors":[
{
"error":{
"code":501,
"status":"Not Implemented",
"message":"Trigger manually!",
"details":[
{
"code":12,
"status":"Unimplemented",
"message":"Trigger manually!"
},
{
"code":2,
"status":"Unknown",
"message":"this is detail"
}
]
}
}
]
}
}
自定义样式#
请实现如下两个接口,并注册到 Entry 中。
type ErrorInterface interface {
Error() string
Code() int
Message() string
Details() []interface{}
}
type ErrorBuilder interface {
New(code int, msg string, details ...interface{}) ErrorInterface
NewCustom() ErrorInterface
}
- main.go
package main
import (
"context"
"errors"
"fmt"
"github.com/rookie-ninja/rk-boot/v2"
"github.com/rookie-ninja/rk-demo/api/gen/v1"
"github.com/rookie-ninja/rk-entry/v2/error"
"github.com/rookie-ninja/rk-entry/v2/middleware"
"github.com/rookie-ninja/rk-grpc/v2/boot"
"github.com/rookie-ninja/rk-grpc/v2/boot/error"
"google.golang.org/grpc"
"net/http"
)
func main() {
boot := rkboot.NewBoot()
// register grpc
entry := rkgrpc.GetGrpcEntry("greeter")
entry.AddRegFuncGrpc(registerGreeter)
entry.AddRegFuncGw(greeter.RegisterGreeterHandlerFromEndpoint)
// Bootstrap
boot.Bootstrap(context.TODO())
// Set default error builder after bootstrap
rkmid.SetErrorBuilder(&MyErrorBuilder{})
// Wait for shutdown sig
boot.WaitForShutdownSig(context.TODO())
}
func registerGreeter(server *grpc.Server) {
greeter.RegisterGreeterServer(server, &GreeterServer{})
}
//GreeterServer GreeterServer struct
type GreeterServer struct{}
// Hello response with hello message
func (server *GreeterServer) Hello(_ context.Context, _ *greeter.HelloRequest) (*greeter.HelloResponse, error) {
return nil, rkgrpcerr.Unimplemented("Trigger manually!", errors.New("this is detail")).Err()
}
type MyError struct {
ErrCode int
ErrMsg string
ErrDetails []interface{}
}
func (m MyError) Error() string {
return fmt.Sprintf("%d-%s", m.ErrCode, m.ErrMsg)
}
func (m MyError) Code() int {
return m.ErrCode
}
func (m MyError) Message() string {
return m.ErrMsg
}
func (m MyError) Details() []interface{} {
return m.ErrDetails
}
type MyErrorBuilder struct{}
func (m *MyErrorBuilder) New(code int, msg string, details ...interface{}) rkerror.ErrorInterface {
return &MyError{
ErrCode: code,
ErrMsg: msg,
ErrDetails: details,
}
}
func (m *MyErrorBuilder) NewCustom() rkerror.ErrorInterface {
return &MyError{
ErrCode: http.StatusInternalServerError,
ErrMsg: "Internal Error",
ErrDetails: []interface{}{},
}
}
{
"ErrCode":501,
"ErrMsg":"Trigger manually!",
"ErrDetails":[
{
"code":12,
"status":"Unimplemented",
"message":"Trigger manually!"
},
{
"code":2,
"status":"Unknown",
"message":"this is detail"
}
]
}