Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add warp and cause interface #23

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Folders
_obj
_test
.idea

# Architecture specific extensions/prefixes
*.[568vq]
Expand Down
73 changes: 0 additions & 73 deletions terror_dsl.go

This file was deleted.

160 changes: 114 additions & 46 deletions terror_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,21 @@ import (
"runtime"
"strconv"
"strings"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

// ErrCode represents a specific error type in a error class.
// Same error code can be used in different error classes.
type ErrCode int

// ErrCodeText is a textual error code that represents a specific error type in a error class.
type ErrCodeText string

type ErrorID string
type RFCErrorCode string

// Error is the 'prototype' of a type of errors.
// Use DefineError to make a *Error:
// var ErrUnavailable = ClassRegion.DefineError().
Expand All @@ -46,29 +59,25 @@ import (
// // handle this error.
// }
type Error struct {
class *ErrClass
code ErrCode
code ErrCode
// codeText is the textual describe of the error code
codeText ErrCodeText
// message is a template of the description of this error.
// printf-style formatting is enabled.
message string
// The workaround field: how to work around this error.
// It's used to teach the users how to solve the error if occurring in the real environment.
Workaround string
workaround string
// Description is the expanded detail of why this error occurred.
// This could be written by developer at a static env,
// and the more detail this field explaining the better,
// even some guess of the cause could be included.
Description string
args []interface{}
file string
line int
}

// Class returns ErrClass
func (e *Error) Class() *ErrClass {
return e.class
description string
// Cause is used to warp some third party error.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

cause error
args []interface{}
file string
line int
}

// Code returns the numeric code of this error.
Expand All @@ -84,23 +93,7 @@ func (e *Error) Code() ErrCode {
// The error code is a 3-tuple of abbreviated component name, error class and error code,
// joined by a colon like {Component}:{ErrorClass}:{InnerErrorCode}.
func (e *Error) RFCCode() RFCErrorCode {
ec := e.Class()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should return the e.codeText if the e.codeText had been assigned a value.

if ec == nil {
return RFCErrorCode(e.ID())
}
reg := ec.registry
// Maybe top-level errors.
if reg.Name == "" {
return RFCErrorCode(fmt.Sprintf("%s:%s",
ec.Description,
e.ID(),
))
}
return RFCErrorCode(fmt.Sprintf("%s:%s:%s",
reg.Name,
ec.Description,
e.ID(),
))
return RFCErrorCode(e.ID())
}

// ID returns the ID of this error.
Expand All @@ -122,16 +115,22 @@ func (e *Error) MessageTemplate() string {
return e.message
}

// SetErrCodeText sets the text error code for standard error.
func (e *Error) SetErrCodeText(codeText string) *Error {
e.codeText = ErrCodeText(codeText)
return e
}

// Error implements error interface.
func (e *Error) Error() string {
describe := e.codeText
if len(describe) == 0 {
describe = ErrCodeText(strconv.Itoa(int(e.code)))
}
return fmt.Sprintf("[%s] %s", e.RFCCode(), e.getMsg())
return fmt.Sprintf("[%s] %s", e.RFCCode(), e.GetMsg())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use the local variable describe as the prefix of the error message.

}

func (e *Error) getMsg() string {
func (e *Error) GetMsg() string {
if len(e.args) > 0 {
return fmt.Sprintf(e.message, e.args...)
}
Expand Down Expand Up @@ -197,9 +196,8 @@ func (e *Error) Equal(err error) bool {
if !ok {
return false
}
classEquals := e.class.Equal(inErr.class)
idEquals := e.ID() == inErr.ID()
return classEquals && idEquals
return idEquals
}

// NotEqual checks if err is not equal to e.
Expand Down Expand Up @@ -239,7 +237,6 @@ type jsonError struct {
Error string `json:"message"`
Description string `json:"description,omitempty"`
Workaround string `json:"workaround,omitempty"`
Class ErrClassID `json:"classID"`
File string `json:"file"`
Line int `json:"line"`
}
Expand All @@ -251,11 +248,10 @@ type jsonError struct {
// This function is reserved for compatibility.
func (e *Error) MarshalJSON() ([]byte, error) {
return json.Marshal(&jsonError{
Error: e.getMsg(),
Description: e.Description,
Workaround: e.Workaround,
Error: e.GetMsg(),
Description: e.description,
Workaround: e.workaround,
RFCCode: e.RFCCode(),
Class: e.class.ID,
Line: e.line,
File: e.file,
})
Expand All @@ -273,9 +269,7 @@ func (e *Error) UnmarshalJSON(data []byte) error {
return Trace(err)
}
codes := strings.Split(string(err.RFCCode), ":")
regName := codes[0]
className := codes[1]
innerCode := codes[2]
innerCode := codes[0]
if i, errAtoi := strconv.Atoi(innerCode); errAtoi == nil {
e.code = ErrCode(i)
} else {
Expand All @@ -284,10 +278,84 @@ func (e *Error) UnmarshalJSON(data []byte) error {
e.line = err.Line
e.file = err.File
e.message = err.Error
e.class = &ErrClass{
Description: className,
ID: err.Class,
registry: &Registry{Name: regName},
}
return nil
}

func (e *Error) Wrap(err error) *Error {
e.cause = err
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should return a new instance of when Wrap a message instead of in-place changing the original instance. The error will be a global instance in many cases.

return e
}

func (e *Error) Cause() error {
root := Unwrap(e.cause)
if root == nil {
return e.cause
}
return root
}

func (e *Error) FastGenWithCause(args ...interface{}) error {
err := *e
err.message = e.cause.Error()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will panic here is Wrap a nil error

err.args = args
return SuspendStack(&err)
}

func (e *Error) GenWithStackByCause(args ...interface{}) error {
err := *e
err.message = e.cause.Error()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

err.args = args
err.fillLineAndFile(1)
return AddStack(&err)
}

type NormalizeOption func(*Error)

// Description returns a NormalizeOption to set description.
func Description(desc string) NormalizeOption {
return func(e *Error) {
e.description = desc
}
}

// Workaround returns a NormalizeOption to set workaround.
func Workaround(wr string) NormalizeOption {
return func(e *Error) {
e.workaround = wr
}
}

// RFCCodeText returns a NormalizeOption to set RFC error code.
func RFCCodeText(codeText string) NormalizeOption {
return func(e *Error) {
e.codeText = ErrCodeText(codeText)
}
}

// MySQLErrorCode returns a NormalizeOption to set error code.
func MySQLErrorCode(code int) NormalizeOption {
return func(e *Error) {
e.code = ErrCode(code)
}
}

// Normalize creates a new Error object.
func Normalize(message string, opts ...NormalizeOption) *Error {
e := &Error{
message: message,
}
for _, opt := range opts {
opt(e)
}
return e
}

// CauseError returns zap.Field contains cause error.
func CauseError(err *Error) zap.Field {
return zap.Field{Key: "error", Type: zapcore.ErrorType, Interface: err.FastGenWithCause()}
}

// CauseError returns zap.Field contains error.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// CauseError returns zap.Field contains error.
// DetailError returns zap.Field contains error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I think we can combine these two methods? And Wrap the cause error inside of this function so that we can directly use something like CauseError(err, causeErr).

func DetailError(err *Error) zap.Field {
return zap.Field{Key: "error", Type: zapcore.ErrorType, Interface: err.FastGenByArgs()}
}
Comment on lines +354 to +361
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to discussion with @rleungx, we can remove this two functions and use zap.Error directly. And PD will customize a function to adopt their scenarios.

Loading