自定义error在grpc service实践

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,

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350