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_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 f6b147e..cbd08b3 100644 --- a/terror_error.go +++ b/terror_error.go @@ -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(). @@ -46,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. @@ -55,20 +67,17 @@ 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 - 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. + cause error + args []interface{} + file string + line int } // Code returns the numeric code of this error. @@ -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() - 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. @@ -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()) } -func (e *Error) getMsg() string { +func (e *Error) GetMsg() string { if len(e.args) > 0 { return fmt.Sprintf(e.message, e.args...) } @@ -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. @@ -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"` } @@ -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, }) @@ -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 { @@ -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 + 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() + 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) +} + +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. +func DetailError(err *Error) zap.Field { + return zap.Field{Key: "error", Type: zapcore.ErrorType, Interface: err.FastGenByArgs()} +} 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 71ceffa..3185cba 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,19 +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") -) - const ( CodeExecResultIsEmpty errors.ErrCode = 3 CodeMissConnectionID errors.ErrCode = 1 @@ -62,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.Normalize("json test", errors.MySQLErrorCode(int(CodeExecResultIsEmpty))) buf, err := json.Marshal(prevTErr) c.Assert(err, IsNil) var curTErr errors.Error @@ -102,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.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() @@ -168,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.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.Normalize("nothing", errors.RFCCodeText("Blank:B1"), errors.Workaround(`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) @@ -339,3 +156,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 := 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] load from etcd meet error") +}