自定義error在grpc service實(shí)踐

grpc error code是有限的,并不能cover用戶需求,因此自定義error,結(jié)合grpc 提供的接口進(jìn)行擴(kuò)展,下面是一些簡(jiǎn)單的代碼實(shí)踐。

代碼目錄:

$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,

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容