From 2493a227ee44ea1fac37644de31bdb9ce12ebfe7 Mon Sep 17 00:00:00 2001 From: imtbkcat Date: Fri, 7 Aug 2020 13:47:39 +0800 Subject: [PATCH 1/8] add warp and cause interface --- .gitignore | 1 + terror_error.go | 32 +++++++++++++++++++++++++++++--- terror_test/terror_test.go | 9 +++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index daf913b..0c9e329 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Folders _obj _test +.idea # Architecture specific extensions/prefixes *.[568vq] diff --git a/terror_error.go b/terror_error.go index f6b147e..a65a1ee 100644 --- a/terror_error.go +++ b/terror_error.go @@ -61,9 +61,11 @@ type Error struct { // 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 + // Cause is used to warp some third party error. + cause error + args []interface{} + file string + line int } // Class returns ErrClass @@ -291,3 +293,27 @@ func (e *Error) UnmarshalJSON(data []byte) error { } return nil } + +func (e *Error) WarpCauseError(err error) *Error { + e.cause = err + return e +} + +func (e *Error) Cause() error { + return e.cause +} + +func (e *Error) FastGenWithCause(args ...interface{}) error { + err := *e + err.message = e.cause.Error() + err.args = args + return SuspendStack(&err) +} + +func (e *Error) GenWithStackByCause(args ...interface{}) error { + err := *e + err.message = e.cause.Error() + err.args = args + err.fillLineAndFile(1) + return AddStack(&err) +} diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index 71ceffa..757c0f2 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -39,6 +39,7 @@ var ( ClassParser = reg.RegisterErrorClass(11, "parser") ClassServer = reg.RegisterErrorClass(15, "server") ClassTable = reg.RegisterErrorClass(19, "table") + ClassMember = reg.RegisterErrorClass(20, "member") ) const ( @@ -339,3 +340,11 @@ func (*testTErrorSuite) TestLineAndFile(c *C) { c.Assert(file2, Equals, f2) c.Assert(line2, Equals, l2-1) } + +func (*testTErrorSuite) TestWarpAndField(c *C) { + causeErr := errors.New("load from etcd meet error") + ErrGetLeader := ClassMember.DefineError().TextualCode("ErrGetLeader").MessageTemplate("fail to get leader").Build() + errWithWarpedCause := ErrGetLeader.WarpCauseError(causeErr) + c.Assert(errWithWarpedCause.FastGenWithCause().Error(), Equals, "[DB:member:ErrGetLeader] load from etcd meet error") + c.Assert(fmt.Sprintf("%v", errWithWarpedCause.FastGenWithCause()), Equals, "[DB:member:ErrGetLeader] fail to get leader") +} From b667838eacc34b6b8b6accd33c8b7f310a715be0 Mon Sep 17 00:00:00 2001 From: imtbkcat Date: Fri, 7 Aug 2020 14:25:46 +0800 Subject: [PATCH 2/8] add zap Field produce method --- terror_error.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/terror_error.go b/terror_error.go index a65a1ee..289eee8 100644 --- a/terror_error.go +++ b/terror_error.go @@ -16,6 +16,8 @@ package errors import ( "encoding/json" "fmt" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" "runtime" "strconv" "strings" @@ -317,3 +319,11 @@ func (e *Error) GenWithStackByCause(args ...interface{}) error { err.fillLineAndFile(1) return AddStack(&err) } + +func CauseError(err *Error) zap.Field { + return zap.Field{Key: "error", Type: zapcore.ErrorType, Interface: err.FastGenWithCause()} +} + +func DetailError(err *Error) zap.Field { + return zap.Field{Key: "error", Type: zapcore.ErrorType, Interface: err.FastGenByArgs()} +} From b17b426e6c337e0d09230cbd49862078912705de Mon Sep 17 00:00:00 2001 From: imtbkcat Date: Fri, 7 Aug 2020 14:26:40 +0800 Subject: [PATCH 3/8] format import --- terror_error.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/terror_error.go b/terror_error.go index 289eee8..8f1fd56 100644 --- a/terror_error.go +++ b/terror_error.go @@ -16,11 +16,12 @@ package errors import ( "encoding/json" "fmt" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" "runtime" "strconv" "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) // Error is the 'prototype' of a type of errors. From 2548b76ac918827a79c0694de222ad9f98b5f4ae Mon Sep 17 00:00:00 2001 From: imtbkcat Date: Mon, 10 Aug 2020 15:38:11 +0800 Subject: [PATCH 4/8] address comment --- terror_error.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/terror_error.go b/terror_error.go index 8f1fd56..dbe74f8 100644 --- a/terror_error.go +++ b/terror_error.go @@ -297,13 +297,17 @@ func (e *Error) UnmarshalJSON(data []byte) error { return nil } -func (e *Error) WarpCauseError(err error) *Error { +func (e *Error) Warp(err error) *Error { e.cause = err return e } func (e *Error) Cause() error { - return e.cause + root := Unwrap(e.cause) + if root == nil { + return e.cause + } + return root } func (e *Error) FastGenWithCause(args ...interface{}) error { From a28915e9ff0c38c98f1def88fbb7b686704c5b38 Mon Sep 17 00:00:00 2001 From: imtbkcat Date: Mon, 10 Aug 2020 15:40:55 +0800 Subject: [PATCH 5/8] fix typo --- terror_error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terror_error.go b/terror_error.go index dbe74f8..473f951 100644 --- a/terror_error.go +++ b/terror_error.go @@ -297,7 +297,7 @@ func (e *Error) UnmarshalJSON(data []byte) error { return nil } -func (e *Error) Warp(err error) *Error { +func (e *Error) Wrap(err error) *Error { e.cause = err return e } From 2d79068bad1428a0ea25ca01da3618c37d7c94bc Mon Sep 17 00:00:00 2001 From: imtbkcat Date: Mon, 10 Aug 2020 19:23:59 +0800 Subject: [PATCH 6/8] remove registry and error class --- terror_dsl.go | 73 ------------- terror_error.go | 90 +++++++++------- terror_gen.go | 79 -------------- terror_registry.go | 176 ------------------------------- terror_test/terror_test.go | 210 +++---------------------------------- 5 files changed, 64 insertions(+), 564 deletions(-) delete mode 100644 terror_dsl.go delete mode 100644 terror_gen.go delete mode 100644 terror_registry.go diff --git a/terror_dsl.go b/terror_dsl.go deleted file mode 100644 index e2e55ee..0000000 --- a/terror_dsl.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -package errors - -import ( - "github.com/pingcap/log" - "go.uber.org/zap" -) - -// Builder is the builder of Error. -type Builder struct { - err *Error - class *ErrClass -} - -// TextualCode is is the textual identity of this error internally, -// note that this error code can only be duplicated in different Registry or ErrorClass. -func (b *Builder) TextualCode(text ErrCodeText) *Builder { - b.err.codeText = text - return b -} - -// NumericCode is is the numeric identity of this error internally, -// note that this error code can only be duplicated in different Registry or ErrorClass. -func (b *Builder) NumericCode(num ErrCode) *Builder { - b.err.code = num - return b -} - -// 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. -func (b *Builder) Description(desc string) *Builder { - b.err.Description = desc - return b -} - -// Workaround shows how to work around this error. -// It's used to teach the users how to solve the error if occurring in the real environment. -func (b *Builder) Workaround(wd string) *Builder { - b.err.Workaround = wd - return b -} - -// MessageTemplate is the template of the error string that can be formatted after -// calling `GenWithArgs` method. -// currently, printf style template is used. -func (b *Builder) MessageTemplate(template string) *Builder { - b.err.message = template - return b -} - -// Build ends the define of the error. -func (b *Builder) Build() *Error { - if ok := b.class.RegisterError(b.err); !ok { - log.Panic("replicated error prototype created", - zap.String("ID", string(b.err.ID())), - zap.String("RFCCode", string(b.err.RFCCode()))) - } - return b.err -} diff --git a/terror_error.go b/terror_error.go index 473f951..4f7233f 100644 --- a/terror_error.go +++ b/terror_error.go @@ -24,6 +24,16 @@ import ( "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(). @@ -49,8 +59,7 @@ 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. @@ -71,11 +80,6 @@ type Error struct { line int } -// Class returns ErrClass -func (e *Error) Class() *ErrClass { - return e.class -} - // Code returns the numeric code of this error. // ID() will return textual error if there it is, // when you just want to get the purely numeric error @@ -89,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() - 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. @@ -127,16 +115,50 @@ func (e *Error) MessageTemplate() string { return e.message } +// NewError creates a standard error object. +func NewError(code int, message string) *Error { + return &Error{ + code: ErrCode(code), + message: message, + } +} + +// NewErrorWithText creates a standard error object using text error code. +func NewErrorWithText(code string, message string) *Error { + return &Error{ + codeText: ErrCodeText(code), + message: message, + } +} + +// SetWorkaround sets the workaround for standard error. +func (e *Error) SetWorkaround(workaround string) *Error { + e.Workaround = workaround + return e +} + +// SetWorkaround sets the description for standard error. +func (e *Error) SetDescription(description string) *Error { + e.Description = description + return e +} + +// 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()) } -func (e *Error) getMsg() string { +func (e *Error) GetMsg() string { if len(e.args) > 0 { return fmt.Sprintf(e.message, e.args...) } @@ -202,9 +224,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. @@ -244,7 +265,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"` } @@ -256,11 +276,10 @@ type jsonError struct { // This function is reserved for compatibility. func (e *Error) MarshalJSON() ([]byte, error) { return json.Marshal(&jsonError{ - Error: e.getMsg(), + Error: e.GetMsg(), Description: e.Description, Workaround: e.Workaround, RFCCode: e.RFCCode(), - Class: e.class.ID, Line: e.line, File: e.file, }) @@ -278,9 +297,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 { @@ -289,11 +306,6 @@ 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 } diff --git a/terror_gen.go b/terror_gen.go deleted file mode 100644 index 71d9a90..0000000 --- a/terror_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2020 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -package errors - -import ( - "fmt" - "github.com/pingcap/log" - "go.uber.org/zap" - "io" - "regexp" -) - -const tomlTemplate = `[error.%s] -error = '''%s''' -description = '''%s''' -workaround = '''%s''' -` - -func (e *Error) exportTo(writer io.Writer) error { - desc := e.Description - if e.Description == "" { - log.Warn("error description missed", zap.String("error", string(e.RFCCode()))) - desc = "N/A" - } - workaround := e.Workaround - if e.Workaround == "" { - log.Warn("error workaround missed", zap.String("error", string(e.RFCCode()))) - workaround = "N/A" - } - _, err := fmt.Fprintf(writer, tomlTemplate, e.RFCCode(), replaceFlags(e.MessageTemplate()), desc, workaround) - return err -} - -func (ec *ErrClass) exportTo(writer io.Writer) error { - for _, e := range ec.AllErrors() { - if err := e.exportTo(writer); err != nil { - return err - } - } - return nil -} - -// ExportTo export the registry to a writer, as toml format from the RFC. -func (r *Registry) ExportTo(writer io.Writer) error { - for _, ec := range r.errClasses { - if err := ec.exportTo(writer); err != nil { - return err - } - } - return nil -} - -// CPP printf flag with minimal support for explicit argument indexes. -// introductory % character: % -// (optional) one or more flags that modify the behavior of the conversion: [+\-# 0]? -// (optional) integer value or * that specifies minimum field width: ([0-9]+|(\[[0-9]+])?\*)? -// (optional) . followed by integer number or *, or neither that specifies precision of the conversion: (\.([0-9]+|(\[[0-9]+])?\*))? -// the prepending (\[[0-9]+])? is for golang explicit argument indexes: -// The same notation before a '*' for a width or precision selects the argument index holding the value. -// (optional) the notation [n] immediately before the verb indicates -// that the nth one-indexed argument is to be formatted instead: (\[[0-9]+])? -// conversion format specifier: [vTtbcdoOqxXUeEfFgGsp] -// %% shouldn't be replaced. -var flagRe, _ = regexp.Compile(`%[+\-# 0]?([0-9]+|(\[[0-9]+])?\*)?(\.([0-9]+|(\[[0-9]+])?\*))?(\[[0-9]+])?[vTtbcdoOqxXUeEfFgGsp]`) - -func replaceFlags(origin string) string { - return string(flagRe.ReplaceAll([]byte(origin), []byte("{placeholder}"))) -} diff --git a/terror_registry.go b/terror_registry.go deleted file mode 100644 index 1268563..0000000 --- a/terror_registry.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2020 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -package errors - -import ( - "fmt" -) - -// Registry is a set of errors for a component. -type Registry struct { - Name string - errClasses map[ErrClassID]ErrClass -} - -// 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 - -// ErrClass represents a class of errors. -// You can create error 'prototypes' of this class. -type ErrClass struct { - ID ErrClassID - Description string - errors map[ErrorID]*Error - registry *Registry -} - -type ErrorID string -type ErrClassID int -type RFCErrorCode string - -// NewRegistry make a registry, where ErrClasses register to. -// One component should create only one registry, named by the RFC. -// For TiDB ecosystem components, when creating registry, -// please use the component name to name the registry, see below example: -// -// TiKV: KV -// PD: PD -// DM: DM -// BR: BR -// TiCDC: CDC -// Lightning: LN -// TiFlash: FLASH -// Dumpling: DP -func NewRegistry(name string) *Registry { - return &Registry{Name: name, errClasses: map[ErrClassID]ErrClass{}} -} - -// RegisterErrorClass registers new error class for terror. -func (r *Registry) RegisterErrorClass(classCode int, desc string) ErrClass { - code := ErrClassID(classCode) - if _, exists := r.errClasses[code]; exists { - panic(fmt.Sprintf("duplicate register ClassCode %d - %s", code, desc)) - } - errClass := ErrClass{ - ID: code, - Description: desc, - errors: map[ErrorID]*Error{}, - registry: r, - } - r.errClasses[code] = errClass - return errClass -} - -// String implements fmt.Stringer interface. -func (ec *ErrClass) String() string { - return ec.Description -} - -// Equal tests whether the other error is in this class. -func (ec *ErrClass) Equal(other *ErrClass) bool { - if ec == nil || other == nil { - return ec == other - } - return ec.ID == other.ID -} - -// EqualClass returns true if err is *Error with the same class. -func (ec *ErrClass) EqualClass(err error) bool { - e := Cause(err) - if e == nil { - return false - } - if te, ok := e.(*Error); ok { - return te.class.Equal(ec) - } - return false -} - -// NotEqualClass returns true if err is not *Error with the same class. -func (ec *ErrClass) NotEqualClass(err error) bool { - return !ec.EqualClass(err) -} - -// New defines an *Error with an error code and an error message. -// Usually used to create base *Error. -// This function is reserved for compatibility, if possible, use DefineError instead. -func (ec *ErrClass) New(code ErrCode, message string) *Error { - return ec.DefineError(). - NumericCode(code). - MessageTemplate(message). - Build() -} - -// DefineError is the entrance of the define error DSL, -// simple usage: -// ``` -// ClassExecutor.DefineError(). -// TextualCode("ExecutorAbsent"). -// MessageTemplate("executor is taking vacation at %s"). -// Build() -// ``` -func (ec *ErrClass) DefineError() *Builder { - return &Builder{ - err: &Error{}, - class: ec, - } -} - -// RegisterError try to register an error to a class. -// return true if success. -func (ec *ErrClass) RegisterError(err *Error) bool { - if _, ok := ec.errors[err.ID()]; ok { - return false - } - err.class = ec - ec.errors[err.ID()] = err - return true -} - -// AllErrors returns all errors of this ErrClass -// Note this isn't thread-safe. -// You shouldn't modify the returned slice without copying. -func (ec *ErrClass) AllErrors() []*Error { - all := make([]*Error, 0, len(ec.errors)) - for _, err := range ec.errors { - all = append(all, err) - } - return all -} - -// AllErrorClasses returns all errClasses that has been registered. -// Note this isn't thread-safe. -func (r *Registry) AllErrorClasses() []ErrClass { - all := make([]ErrClass, 0, len(r.errClasses)) - for _, errClass := range r.errClasses { - all = append(all, errClass) - } - return all -} - -// Synthesize synthesizes an *Error in the air -// it didn't register error into ErrClass -// so it's goroutine-safe -// and often be used to create Error came from other systems like TiKV. -func (ec *ErrClass) Synthesize(code ErrCode, message string) *Error { - return &Error{ - class: ec, - code: code, - message: message, - } -} diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index 757c0f2..82b38e8 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -14,12 +14,10 @@ package terror_test import ( - "bytes" "encoding/json" "fmt" "os" "runtime" - "sort" "strings" "testing" "time" @@ -28,20 +26,6 @@ import ( "github.com/pingcap/errors" ) -// Error classes. -// Those fields below are copied from the original version of terror, -// so that we can reuse those test cases. -var ( - reg = errors.NewRegistry("DB") - ClassExecutor = reg.RegisterErrorClass(5, "executor") - ClassKV = reg.RegisterErrorClass(8, "kv") - ClassOptimizer = reg.RegisterErrorClass(10, "planner") - ClassParser = reg.RegisterErrorClass(11, "parser") - ClassServer = reg.RegisterErrorClass(15, "server") - ClassTable = reg.RegisterErrorClass(19, "table") - ClassMember = reg.RegisterErrorClass(20, "member") -) - const ( CodeExecResultIsEmpty errors.ErrCode = 3 CodeMissConnectionID errors.ErrCode = 1 @@ -63,37 +47,8 @@ func (s *testTErrorSuite) TestErrCode(c *C) { c.Assert(CodeResultUndetermined, Equals, errors.ErrCode(2)) } -func (s *testTErrorSuite) TestTError(c *C) { - c.Assert(ClassParser.String(), Not(Equals), "") - c.Assert(ClassOptimizer.String(), Not(Equals), "") - c.Assert(ClassKV.String(), Not(Equals), "") - c.Assert(ClassServer.String(), Not(Equals), "") - - parserErr := ClassParser.New(errors.ErrCode(100), "error 100") - c.Assert(parserErr.Error(), Not(Equals), "") - c.Assert(ClassParser.EqualClass(parserErr), IsTrue) - c.Assert(ClassParser.NotEqualClass(parserErr), IsFalse) - - c.Assert(ClassOptimizer.EqualClass(parserErr), IsFalse) - optimizerErr := ClassOptimizer.New(errors.ErrCode(2), "abc") - c.Assert(ClassOptimizer.EqualClass(errors.New("abc")), IsFalse) - c.Assert(ClassOptimizer.EqualClass(nil), IsFalse) - c.Assert(optimizerErr.Equal(optimizerErr.GenWithStack("def")), IsTrue) - c.Assert(optimizerErr.Equal(errors.Trace(optimizerErr.GenWithStack("def"))), IsTrue) - c.Assert(optimizerErr.Equal(nil), IsFalse) - c.Assert(optimizerErr.Equal(errors.New("abc")), IsFalse) - - // Test case for FastGen. - c.Assert(optimizerErr.Equal(optimizerErr.FastGen("def")), IsTrue) - c.Assert(optimizerErr.Equal(optimizerErr.FastGen("def: %s", "def")), IsTrue) - kvErr := ClassKV.New(1062, "key already exist") - e := kvErr.FastGen("Duplicate entry '%d' for key 'PRIMARY'", 1) - c.Assert(e, NotNil) - c.Assert(e.Error(), Equals, "[DB:kv:1062] Duplicate entry '1' for key 'PRIMARY'") -} - func (s *testTErrorSuite) TestJson(c *C) { - prevTErr := ClassTable.New(CodeExecResultIsEmpty, "json test") + prevTErr := errors.NewError(int(CodeExecResultIsEmpty), "json test") buf, err := json.Marshal(prevTErr) c.Assert(err, IsNil) var curTErr errors.Error @@ -103,11 +58,8 @@ func (s *testTErrorSuite) TestJson(c *C) { c.Assert(isEqual, IsTrue) } -var predefinedErr = ClassExecutor.New(errors.ErrCode(123), "predefiend error") -var predefinedTextualErr = ClassExecutor.DefineError(). - TextualCode("ExecutorAbsent"). - MessageTemplate("executor is taking vacation at %s"). - Build() +var predefinedErr = errors.NewError(123, "predefiend error") +var predefinedTextualErr = errors.NewErrorWithText("executor:ExecutorAbsent", "executor is taking vacation at %s") func example() error { err := call() @@ -169,160 +121,24 @@ func (s *testTErrorSuite) TestErrorEqual(c *C) { c.Assert(errors.ErrorEqual(nil, nil), IsTrue) c.Assert(errors.ErrorNotEqual(e1, e6), IsTrue) - code1 := errors.ErrCode(9001) - code2 := errors.ErrCode(9002) - te1 := ClassParser.Synthesize(code1, "abc") - te3 := ClassKV.New(code1, "abc") - te4 := ClassKV.New(code2, "abc") - c.Assert(errors.ErrorEqual(te1, te3), IsFalse) - c.Assert(errors.ErrorEqual(te3, te4), IsFalse) } func (s *testTErrorSuite) TestNewError(c *C) { today := time.Now().Weekday().String() err := predefinedTextualErr.GenWithStackByArgs(today) c.Assert(err, NotNil) - c.Assert(err.Error(), Equals, "[DB:executor:ExecutorAbsent] executor is taking vacation at "+today) -} - -func (s *testTErrorSuite) TestAllErrClasses(c *C) { - items := []errors.ErrClass{ - ClassExecutor, ClassKV, ClassOptimizer, ClassParser, ClassServer, ClassTable, - } - registered := reg.AllErrorClasses() - - // sort it to align them. - sort.Slice(items, func(i, j int) bool { - return items[i].ID < items[j].ID - }) - sort.Slice(registered, func(i, j int) bool { - return registered[i].ID < registered[j].ID - }) - - for i := range items { - c.Assert(items[i].ID, Equals, registered[i].ID) - } -} - -func (s *testTErrorSuite) TestErrorExists(c *C) { - origin := ClassParser.DefineError(). - TextualCode("EverythingAlright"). - MessageTemplate("that was a joke, hoo!"). - Build() - - c.Assert(func() { - _ = ClassParser.DefineError(). - TextualCode("EverythingAlright"). - MessageTemplate("that was another joke, hoo!"). - Build() - }, Panics, "replicated error prototype created") - - // difference at either code or text should be different error - changeCode := ClassParser.DefineError(). - NumericCode(4399). - MessageTemplate("that was a joke, hoo!"). - Build() - changeText := ClassParser.DefineError(). - TextualCode("EverythingBad"). - MessageTemplate("that was not a joke, folks!"). - Build() - containsErr := func(e error) bool { - for _, err := range ClassParser.AllErrors() { - if err.Equal(e) { - return true - } - } - return false - } - c.Assert(containsErr(origin), IsTrue) - c.Assert(containsErr(changeCode), IsTrue) - c.Assert(containsErr(changeText), IsTrue) + c.Assert(err.Error(), Equals, "[executor:ExecutorAbsent] executor is taking vacation at "+today) } func (s *testTErrorSuite) TestRFCCode(c *C) { - reg := errors.NewRegistry("TEST") - errc1 := reg.RegisterErrorClass(1, "TestErr1") - errc2 := reg.RegisterErrorClass(2, "TestErr2") - c1err1 := errc1.DefineError(). - TextualCode("Err1"). - MessageTemplate("nothing"). - Build() - c2err2 := errc2.DefineError(). - TextualCode("Err2"). - MessageTemplate("nothing"). - Build() - c.Assert(c1err1.RFCCode(), Equals, errors.RFCErrorCode("TEST:TestErr1:Err1")) - c.Assert(c2err2.RFCCode(), Equals, errors.RFCErrorCode("TEST:TestErr2:Err2")) - blankReg := errors.NewRegistry("") - errb := blankReg.RegisterErrorClass(1, "Blank") - berr := errb.DefineError(). - TextualCode("B1"). - MessageTemplate("nothing"). - Workaround(`Do nothing`). - Build() + c1err1 := errors.NewErrorWithText("TestErr1:Err1", "nothing") + c2err2 := errors.NewErrorWithText("TestErr2:Err2", "nothing") + c.Assert(c1err1.RFCCode(), Equals, errors.RFCErrorCode("TestErr1:Err1")) + c.Assert(c2err2.RFCCode(), Equals, errors.RFCErrorCode("TestErr2:Err2")) + berr := errors.NewErrorWithText("Blank:B1", "nothing").SetWorkaround(`Do nothing`) c.Assert(berr.RFCCode(), Equals, errors.RFCErrorCode("Blank:B1")) } -const ( - somewhatErrorTOML = `[error.KV:Somewhat:Foo] -error = '''some {placeholder} thing happened, and some {placeholder} goes verbose. I'm {placeholder} percent confusing... -Maybe only {placeholder} peaces of placeholders can save me... Oh my {placeholder}.{placeholder}!''' -description = '''N/A''' -workaround = '''N/A''' -` - err8005TOML = `[error.KV:2PC:8005] -error = '''Write Conflict, txnStartTS is stale''' -description = '''A certain Raft Group is not available, such as the number of replicas is not enough. -This error usually occurs when the TiKV server is busy or the TiKV node is down.''' -` + "workaround = '''Check whether `tidb_disable_txn_auto_retry` is set to `on`. If so, set it to `off`; " + - "if it is already `off`, increase the value of `tidb_retry_limit` until the error no longer occurs.'''\n" - errUnavailableTOML = `[error.KV:Region:Unavailable] -error = '''Region is unavailable''' -description = '''A certain Raft Group is not available, such as the number of replicas is not enough. -This error usually occurs when the TiKV server is busy or the TiKV node is down.''' -workaround = '''Check the status, monitoring data and log of the TiKV server.''' -` -) - -func (*testTErrorSuite) TestExport(c *C) { - RegKV := errors.NewRegistry("KV") - Class2PC := RegKV.RegisterErrorClass(1, "2PC") - _ = Class2PC.DefineError(). - NumericCode(8005). - Description("A certain Raft Group is not available, such as the number of replicas is not enough.\n" + - "This error usually occurs when the TiKV server is busy or the TiKV node is down."). - Workaround("Check whether `tidb_disable_txn_auto_retry` is set to `on`. If so, set it to `off`; " + - "if it is already `off`, increase the value of `tidb_retry_limit` until the error no longer occurs."). - MessageTemplate("Write Conflict, txnStartTS is stale"). - Build() - - ClassRegion := RegKV.RegisterErrorClass(2, "Region") - _ = ClassRegion.DefineError(). - TextualCode("Unavailable"). - Description("A certain Raft Group is not available, such as the number of replicas is not enough.\n" + - "This error usually occurs when the TiKV server is busy or the TiKV node is down."). - Workaround("Check the status, monitoring data and log of the TiKV server."). - MessageTemplate("Region is unavailable"). - Build() - - ClassSomewhat := RegKV.RegisterErrorClass(3, "Somewhat") - _ = ClassSomewhat.DefineError(). - TextualCode("Foo"). - MessageTemplate("some %.6s thing happened, and some %#v goes verbose. I'm %6.3f percent confusing...\n" + - "Maybe only %[3]*.[2]*[1]f peaces of placeholders can save me... Oh my %s.%d!"). - Build() - - result := bytes.NewBuffer([]byte{}) - err := RegKV.ExportTo(result) - c.Assert(err, IsNil) - resultStr := result.String() - fmt.Println("Result: ") - fmt.Print(resultStr) - c.Assert(strings.Contains(resultStr, somewhatErrorTOML), IsTrue) - c.Assert(strings.Contains(resultStr, err8005TOML), IsTrue) - c.Assert(strings.Contains(resultStr, errUnavailableTOML), IsTrue) -} - func (*testTErrorSuite) TestLineAndFile(c *C) { err := predefinedTextualErr.GenWithStackByArgs("everyday") _, f, l, _ := runtime.Caller(0) @@ -343,8 +159,8 @@ func (*testTErrorSuite) TestLineAndFile(c *C) { func (*testTErrorSuite) TestWarpAndField(c *C) { causeErr := errors.New("load from etcd meet error") - ErrGetLeader := ClassMember.DefineError().TextualCode("ErrGetLeader").MessageTemplate("fail to get leader").Build() - errWithWarpedCause := ErrGetLeader.WarpCauseError(causeErr) - c.Assert(errWithWarpedCause.FastGenWithCause().Error(), Equals, "[DB:member:ErrGetLeader] load from etcd meet error") - c.Assert(fmt.Sprintf("%v", errWithWarpedCause.FastGenWithCause()), Equals, "[DB:member:ErrGetLeader] fail to get leader") + ErrGetLeader := errors.NewErrorWithText("member:ErrGetLeader", "fail to get leader") + errWithWarpedCause := ErrGetLeader.Wrap(causeErr) + c.Assert(errWithWarpedCause.FastGenWithCause().Error(), Equals, "[member:ErrGetLeader] load from etcd meet error") + c.Assert(fmt.Sprintf("%v", errWithWarpedCause.FastGenWithCause()), Equals, "[member:ErrGetLeader] fail to get leader") } From 7b34aeb4e57cb3e017604b731916623c8235c709 Mon Sep 17 00:00:00 2001 From: imtbkcat Date: Wed, 12 Aug 2020 17:13:27 +0800 Subject: [PATCH 7/8] add method to generate error instance --- terror_error.go | 67 ++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/terror_error.go b/terror_error.go index 4f7233f..b276475 100644 --- a/terror_error.go +++ b/terror_error.go @@ -67,12 +67,12 @@ type Error struct { 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 + description string // Cause is used to warp some third party error. cause error args []interface{} @@ -115,34 +115,6 @@ func (e *Error) MessageTemplate() string { return e.message } -// NewError creates a standard error object. -func NewError(code int, message string) *Error { - return &Error{ - code: ErrCode(code), - message: message, - } -} - -// NewErrorWithText creates a standard error object using text error code. -func NewErrorWithText(code string, message string) *Error { - return &Error{ - codeText: ErrCodeText(code), - message: message, - } -} - -// SetWorkaround sets the workaround for standard error. -func (e *Error) SetWorkaround(workaround string) *Error { - e.Workaround = workaround - return e -} - -// SetWorkaround sets the description for standard error. -func (e *Error) SetDescription(description string) *Error { - e.Description = description - return e -} - // SetErrCodeText sets the text error code for standard error. func (e *Error) SetErrCodeText(codeText string) *Error { e.codeText = ErrCodeText(codeText) @@ -277,8 +249,8 @@ type jsonError struct { func (e *Error) MarshalJSON() ([]byte, error) { return json.Marshal(&jsonError{ Error: e.GetMsg(), - Description: e.Description, - Workaround: e.Workaround, + Description: e.description, + Workaround: e.workaround, RFCCode: e.RFCCode(), Line: e.line, File: e.file, @@ -337,6 +309,37 @@ func (e *Error) GenWithStackByCause(args ...interface{}) error { return AddStack(&err) } +type NormalizeOption func(*Error) + +func Description(desc string) NormalizeOption { + return func(e *Error) { + e.description = desc + } +} + +func Workaround(wr string) NormalizeOption { + return func(e *Error) { + e.workaround = wr + } +} + +func RFCCodeText(codeText string) NormalizeOption { + return func(e *Error) { + e.codeText = ErrCodeText(codeText) + } +} + +func Normalize(code int, message string, opts ...NormalizeOption) *Error { + e := &Error{ + code: ErrCode(code), + message: message, + } + for _, opt := range opts { + opt(e) + } + return e +} + func CauseError(err *Error) zap.Field { return zap.Field{Key: "error", Type: zapcore.ErrorType, Interface: err.FastGenWithCause()} } From 57ec461934ff91ea785a8ccdb48476fcae1a854a Mon Sep 17 00:00:00 2001 From: imtbkcat Date: Wed, 12 Aug 2020 17:38:36 +0800 Subject: [PATCH 8/8] add comment and fix test --- terror_error.go | 16 ++++++++++++++-- terror_test/terror_test.go | 16 ++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/terror_error.go b/terror_error.go index b276475..cbd08b3 100644 --- a/terror_error.go +++ b/terror_error.go @@ -311,27 +311,37 @@ func (e *Error) GenWithStackByCause(args ...interface{}) error { 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) } } -func Normalize(code int, message string, opts ...NormalizeOption) *Error { +// 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{ - code: ErrCode(code), message: message, } for _, opt := range opts { @@ -340,10 +350,12 @@ func Normalize(code int, message string, opts ...NormalizeOption) *Error { 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. func DetailError(err *Error) zap.Field { return zap.Field{Key: "error", Type: zapcore.ErrorType, Interface: err.FastGenByArgs()} } diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index 82b38e8..3185cba 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -48,7 +48,7 @@ func (s *testTErrorSuite) TestErrCode(c *C) { } func (s *testTErrorSuite) TestJson(c *C) { - prevTErr := errors.NewError(int(CodeExecResultIsEmpty), "json test") + prevTErr := errors.Normalize("json test", errors.MySQLErrorCode(int(CodeExecResultIsEmpty))) buf, err := json.Marshal(prevTErr) c.Assert(err, IsNil) var curTErr errors.Error @@ -58,8 +58,8 @@ func (s *testTErrorSuite) TestJson(c *C) { c.Assert(isEqual, IsTrue) } -var predefinedErr = errors.NewError(123, "predefiend error") -var predefinedTextualErr = errors.NewErrorWithText("executor:ExecutorAbsent", "executor is taking vacation at %s") +var predefinedErr = errors.Normalize("predefiend error", errors.MySQLErrorCode(123)) +var predefinedTextualErr = errors.Normalize("executor is taking vacation at %s", errors.RFCCodeText("executor:ExecutorAbsent")) func example() error { err := call() @@ -131,11 +131,11 @@ func (s *testTErrorSuite) TestNewError(c *C) { } func (s *testTErrorSuite) TestRFCCode(c *C) { - c1err1 := errors.NewErrorWithText("TestErr1:Err1", "nothing") - c2err2 := errors.NewErrorWithText("TestErr2:Err2", "nothing") + c1err1 := errors.Normalize("nothing", errors.RFCCodeText("TestErr1:Err1")) + c2err2 := errors.Normalize("nothing", errors.RFCCodeText("TestErr2:Err2")) c.Assert(c1err1.RFCCode(), Equals, errors.RFCErrorCode("TestErr1:Err1")) c.Assert(c2err2.RFCCode(), Equals, errors.RFCErrorCode("TestErr2:Err2")) - berr := errors.NewErrorWithText("Blank:B1", "nothing").SetWorkaround(`Do nothing`) + berr := errors.Normalize("nothing", errors.RFCCodeText("Blank:B1"), errors.Workaround(`Do nothing`)) c.Assert(berr.RFCCode(), Equals, errors.RFCErrorCode("Blank:B1")) } @@ -159,8 +159,8 @@ func (*testTErrorSuite) TestLineAndFile(c *C) { func (*testTErrorSuite) TestWarpAndField(c *C) { causeErr := errors.New("load from etcd meet error") - ErrGetLeader := errors.NewErrorWithText("member:ErrGetLeader", "fail to get leader") + ErrGetLeader := errors.Normalize("fail to get leader", errors.RFCCodeText("member:ErrGetLeader")) errWithWarpedCause := ErrGetLeader.Wrap(causeErr) c.Assert(errWithWarpedCause.FastGenWithCause().Error(), Equals, "[member:ErrGetLeader] load from etcd meet error") - c.Assert(fmt.Sprintf("%v", errWithWarpedCause.FastGenWithCause()), Equals, "[member:ErrGetLeader] fail to get leader") + c.Assert(fmt.Sprintf("%v", errWithWarpedCause.FastGenWithCause()), Equals, "[member:ErrGetLeader] load from etcd meet error") }