自定义error在grpc service实践

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

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

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