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