分享
  1. 首页
  2. 文章

自定义error在grpc service实践

cli1871 · · 2383 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

grpc error code是有限的,并不能cover用户需求,因此自定义error,结合grpc 提供的接口进行扩展,下面是一些简单的代码实践。

代码目录:

$GOPATH/src/test/utils

子目录 mysqlerrors (test/utils/mysqlerrors):

error_codes.go

package mysqlerrors

const ( // See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html

errDuplicateEntry = 1062 // ER_DUP_ENTRY

errNoReferencedRow2 = 1452 // ER_NO_REFERENCED_ROW_2

)

errors.go

package mysqlerrors

// recordNotUniqueError represents RecordNotUnique error

type recordNotUniqueError struct {

errerror

}

// Error implements error interface

func (e *recordNotUniqueError) Error() string {

return e.err.Error()

}

// Cause implements this interface

//

// type causer interface {

// Cause() error

// }

//

func (e *recordNotUniqueError) Cause() error {

return e.err

}

// RecordNotUnique implements RecordNotUnique interface

func (e *recordNotUniqueError) RecordNotUnique() {}

// invalidForeignKeyError represents InvalidForeignKey error

type invalidForeignKeyError struct {

errerror

}

// Error implements error interface

func (e *invalidForeignKeyError) Error() string {

return e.err.Error()

}

// Cause implements this interface

//

// type causer interface {

// Cause() error

// }

//

func (e *invalidForeignKeyError) Cause() error {

return e.err

}

// InvalidForeignKey implements InvalidForeignKey interface

func (e *invalidForeignKeyError) InvalidForeignKey() {}

get_mysql_error_code.go

package mysqlerrors

import (

"strconv"

"strings"

)

// getMysqlErrorCode get the mysql error code from a *mysql.MySQLError type error

// if error code found, returns code and true. Otherwise, returns 0 and false.

func getMysqlErrorCode(err error) (int, bool) {

// as https://github.com/go-sql-driver/mysql/blob/master/errors.go#L64

codeStr := strings.Split(strings.TrimPrefix(err.Error(),"Error "), ":")[0]

if code, err := strconv.Atoi(codeStr); err == nil {

return code, true

}

return 0, false

}

to_test_error.go

package mysqlerrors

import (

"database/sql"

"errors"

"pkg/testerrors"

perrors"pkg/errors"

"proto"

"status"

gstatus"google.golang.org/grpc/status"

)

// ToTestError translate a general error to Test error.

// See package "pkg/testerrors"

func ToTestError(err error) error {

code, ok := getMysqlErrorCode(perrors.Cause(err))

if !ok {

returnerr

}

switch code {

default:

returnerr

case errDuplicateEntry:

return &recordNotUniqueError{err}

case errNoReferencedRow2:

return &invalidForeignKeyError{err}

}

}

// ToGrpcErrFromTestErr translate Test error to Test grpc error.

// See package "pkg/testerrors"

func ToGrpcErrFromTestErr(err error) error {

if err == nil {

return err

}

switch err.(type) {

default:

return err

case testerrors.RecordNotUnique:

return status.Error(proto.CODE_TEST_ERR_DUPLICATE_ENTRY, err.Error())

case testerrors.InvalidForeignKey:

return status.Error(proto.CODE_TEST_ERR_NO_REFERENCED_ROW2, err.Error())

}

}

// ToTestErrFromGrpcErr translate grpc error to Test error.

// See package "pkg/testerrors"

func ToTestErrFromGrpcErr(err error) error {

if err == nil {

return err

}

s, ok := gstatus.FromError(err)

if !ok {

return errors.New("not a grpc error")

}

details := s.Details()

var testError *proto.StatusList

if len(details) == 0 {

return err

}

testError, ok = s.Details()[0].(*proto.StatusList)

if !ok {

return err

}

if len(testError.Errors) == 0 {

return nil

}

code := testError.Errors[0].Code

switch code {

default:

return err

case proto.CODE_TEST_ERR_DUPLICATE_ENTRY:

return &recordNotUniqueError{err}

case proto.CODE_TEST_ERR_NO_REFERENCED_ROW2:

return &invalidForeignKeyError{err}

}

}



子目录pkg (test/utils/pkg):

pkg/errors/cause.go

package errors

// Cause is copied from https://github.com/pkg/errors/blob/master/errors.go

func Cause(err error) error {

type causer interface {

Cause()error

}

for err != nil {

cause, ok := err.(causer)

if !ok {

break

}

err = cause.Cause()

}

returnerr

}

pkg/testerrors/errors.go

// Package testerrors provides basic interfaces to Test execution errors.

package testerrors

// RecordNotUnique returned when a record cannot be inserted or updated because it would violate a uniqueness constraint.

type RecordNotUnique interface {

RecordNotUnique()

}

// InvalidForeignKey returned when a record cannot be inserted or updated because it references a non-existent record.

type InvalidForeignKey interface {

InvalidForeignKey()

}


子目录proto ():

test/utils/proto/status.proto

syntax = "proto3";

package proto;

enum CODE {

UNKNOWN =0; // http code 500

//Test duplicate entry error

TEST_ERR_DUPLICATE_ENTRY =3600;

//Test no referened row error

TEST_ERR_NO_REFERENCED_ROW2 =3601;

}

message Status {

CODE code =1;

string field = 2;

string message = 3;

string detail = 4;

}

message StatusList {

repeated Status errors = 1;

}

protoc -I=/usr/local/include -I=. --go_out=. status.proto

子目录status:

test/utils/status/status.go

package status

import (

...

"google.golang.org/grpc/codes"

"google.golang.org/grpc/grpclog"

"google.golang.org/grpc/status"

)

type Detail map[string]interface{}

type Status struct {

Code proto.CODE

Field string

Messagestring

Detail Detail

}

// Error builds a single error with code and message.

func Error(code proto.CODE, message string) error {

return errorsWith(grpcCode(code), message, &Status{Code: code, Message: message})

}

func buildMsg(errorList []*Status) (msg string) {

for _, err := range errorList {

msg += err.Message +";"

}

return

}

// Errors builds an error with a grpc Status code and a list of Status.

// The value to "Detail" in each Status MUST be a JSON object.

func errorsWith(c codes.Code, msg string, errorList ...*Status) error {

protoStatusList := asProtoStatus(errorList)

if len(protoStatusList) == 0 {

protoStatusList =append(protoStatusList, &proto.Status{Code: proto.CODE_UNKNOWN, Detail: "{}"})

}

s, err := status.New(c, msg).WithDetails(&proto.StatusList{

Errors: protoStatusList,

})

if err != nil {

grpclog.Print("Error error:", err)

return err

}

return s.Err()

}

func asProtoStatus(errorList []*Status) []*proto.Status {

list :=make([]*proto.Status, 0, len(errorList))

for _, err := range errorList {

detailStr :="{}"

if err.Detail != nil {

detailBytes, e := json.Marshal(err.Detail)

if e == nil {

detailStr =string(detailBytes)

}

}

list =append(list, &proto.Status{

Code: err.Code,

Field: err.Field,

Message: err.Message,

Detail: detailStr,

})

}

return list

}

func grpcCode(code proto.CODE) codes.Code {

c, ok := grpcCodeMap[code]

if ok {

return c

}

return codes.Unknown

}

func grpcCodeFrom(errorList ...*Status) codes.Code {

last := codes.Unknown

for i, e := range errorList {

if i == 0 {

last = grpcCode(e.Code)

continue

}

if last != grpcCode(e.Code) {

return codes.Unknown

}

}

return last

}

var grpcCodeMap = map[proto.CODE]codes.Code{

proto.CODE_UNKNOWN: codes.Unknown,

}


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:cli1871

查看原文:自定义error在grpc service实践

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2383 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏