package error import ( "errors" "fmt" "member/internal/lib/error/code" "net/http" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // TODO Error要移到common 包 // Scope global variable should be set by service or module var Scope = code.Unset type Err struct { category uint32 code uint32 scope uint32 msg string internalErr error } // Error is the interface of error // Getter function of private property "msg" func (e *Err) Error() string { if e == nil { return "" } // chain the error string if the internal err exists var internalErrStr string if e.internalErr != nil { internalErrStr = e.internalErr.Error() } if e.msg != "" { if internalErrStr != "" { return fmt.Sprintf("%s: %s", e.msg, internalErrStr) } return e.msg } generalErrStr := e.GeneralError() if internalErrStr != "" { return fmt.Sprintf("%s: %s", generalErrStr, internalErrStr) } return generalErrStr } // Category getter function of private property "category" func (e *Err) Category() uint32 { if e == nil { return 0 } return e.category } // Scope getter function of private property "scope" func (e *Err) Scope() uint32 { if e == nil { return code.Unset } return e.scope } // CodeStr returns the string of error code with zero padding func (e *Err) CodeStr() string { if e == nil { return "00000" } if e.Category() == code.CatGRPC { return fmt.Sprintf("%d%04d", e.Scope(), e.Category()+e.Code()) } return fmt.Sprintf("%d%04d", e.Scope(), e.Code()) } // Code getter function of private property "code" func (e *Err) Code() uint32 { if e == nil { return code.OK } return e.code } func (e *Err) FullCode() uint32 { if e == nil { return 0 } if e.Category() == code.CatGRPC { return e.Scope()*10000 + e.Category() + e.Code() } return e.Scope()*10000 + e.Code() } // HTTPStatus returns corresponding HTTP status code func (e *Err) HTTPStatus() int { if e == nil || e.Code() == code.OK { return http.StatusOK } // determine status code by code switch e.Code() { case code.ResourceInsufficient: // 400 return http.StatusBadRequest case code.Unauthorized, code.InsufficientPermission: // 401 return http.StatusUnauthorized case code.InsufficientQuota: // 402 return http.StatusPaymentRequired case code.InvalidPosixTime, code.Forbidden: // 403 return http.StatusForbidden case code.ResourceNotFound: // 404 return http.StatusNotFound case code.ResourceAlreadyExist, code.InvalidResourceState: // 409 return http.StatusConflict case code.NotValidImplementation: // 501 return http.StatusNotImplemented default: } // determine status code by category switch e.Category() { case code.CatInput: return http.StatusBadRequest default: // return status code 500 if none of the condition is met return http.StatusInternalServerError } } // GeneralError transform category level error message // It's the general error message for customer/API caller func (e *Err) GeneralError() string { if e == nil { return "" } errStr, ok := code.CatToStr[e.Category()] if !ok { return "" } return errStr } // Is called when performing errors.Is(). // DO NOT USE THIS FUNCTION DIRECTLY unless you are very certain about what you're doing. // Use errors.Is instead. // This function compares if two error variables are both *Err, and have the same code (without checking the wrapped internal error) func (e *Err) Is(f error) bool { var err *Err ok := errors.As(f, &err) if !ok { return false } return e.Code() == err.Code() } // Unwrap returns the underlying error // The result of unwrapping an error may itself have an Unwrap method; // we call the sequence of errors produced by repeated unwrapping the error chain. func (e *Err) Unwrap() error { if e == nil { return nil } return e.internalErr } // Wrap sets the internal error to Err struct func (e *Err) Wrap(internalErr error) *Err { if e != nil { e.internalErr = internalErr } return e } func (e *Err) GRPCStatus() *status.Status { if e == nil { return status.New(codes.OK, "") } return status.New(codes.Code(e.FullCode()), e.Error()) }