diff --git a/errors/gerror/gerror.go b/errors/gerror/gerror.go index 282dcb40a..95a2494a2 100644 --- a/errors/gerror/gerror.go +++ b/errors/gerror/gerror.go @@ -17,36 +17,6 @@ import ( "github.com/gogf/gf/v2/errors/gcode" ) -// iCode is the interface for Code feature. -type iCode interface { - Error() string - Code() gcode.Code -} - -// iStack is the interface for Stack feature. -type iStack interface { - Error() string - Stack() string -} - -// iCause is the interface for Cause feature. -type iCause interface { - Error() string - Cause() error -} - -// iCurrent is the interface for Current feature. -type iCurrent interface { - Error() string - Current() error -} - -// iNext is the interface for Next feature. -type iNext interface { - Error() string - Next() error -} - // New creates and returns an error which is formatted from given text. func New(text string) error { return &Error{ @@ -305,8 +275,39 @@ func Next(err error) error { return nil } +// Unwrap is alias of function `Next`. +// It is just for implements for stdlib errors.Unwrap from Go version 1.17. +func Unwrap(err error) error { + return Next(err) +} + // HasStack checks and returns whether `err` implemented interface `iStack`. func HasStack(err error) bool { _, ok := err.(iStack) return ok } + +// Equal reports whether current error `err` equals to error `target`. +// Please note that, in default comparison for `Error`, +// the errors are considered the same if both the `code` and `text` of them are the same. +func Equal(err, target error) bool { + if err == target { + return true + } + if e, ok := err.(iEqual); ok { + return e.Equal(target) + } + if e, ok := target.(iEqual); ok { + return e.Equal(err) + } + return false +} + +// Is reports whether current error `err` has error `target` in its chaining errors. +// It is just for implements for stdlib errors.Unwrap from Go version 1.17. +func Is(err, target error) bool { + if e, ok := err.(iIs); ok { + return e.Is(target) + } + return false +} diff --git a/errors/gerror/gerror_error.go b/errors/gerror/gerror_error.go index e3491b7bf..5a860d118 100644 --- a/errors/gerror/gerror_error.go +++ b/errors/gerror/gerror_error.go @@ -7,10 +7,8 @@ package gerror import ( - "bytes" "errors" "fmt" - "io" "runtime" "strings" @@ -59,18 +57,6 @@ func (err *Error) Error() string { return errStr } -// Code returns the error code. -// It returns CodeNil if it has no error code. -func (err *Error) Code() gcode.Code { - if err == nil { - return gcode.CodeNil - } - if err.code == gcode.CodeNil { - return Code(err.Next()) - } - return err.code -} - // Cause returns the root cause error. func (err *Error) Cause() error { if err == nil { @@ -97,64 +83,6 @@ func (err *Error) Cause() error { return nil } -// Format formats the frame according to the fmt.Formatter interface. -// -// %v, %s : Print all the error string; -// %-v, %-s : Print current level error string; -// %+s : Print full stack error list; -// %+v : Print the error string and full stack error list; -func (err *Error) Format(s fmt.State, verb rune) { - switch verb { - case 's', 'v': - switch { - case s.Flag('-'): - if err.text != "" { - _, _ = io.WriteString(s, err.text) - } else { - _, _ = io.WriteString(s, err.Error()) - } - case s.Flag('+'): - if verb == 's' { - _, _ = io.WriteString(s, err.Stack()) - } else { - _, _ = io.WriteString(s, err.Error()+"\n"+err.Stack()) - } - default: - _, _ = io.WriteString(s, err.Error()) - } - } -} - -// Stack returns the stack callers as string. -// It returns an empty string if the `err` does not support stacks. -func (err *Error) Stack() string { - if err == nil { - return "" - } - var ( - loop = err - index = 1 - buffer = bytes.NewBuffer(nil) - ) - for loop != nil { - buffer.WriteString(fmt.Sprintf("%d. %-v\n", index, loop)) - index++ - formatSubStack(loop.stack, buffer) - if loop.error != nil { - if e, ok := loop.error.(*Error); ok { - loop = e - } else { - buffer.WriteString(fmt.Sprintf("%d. %s\n", index, loop.error.Error())) - index++ - break - } - } else { - break - } - } - return buffer.String() -} - // Current creates and returns the current level error. // It returns nil if current level error is nil. func (err *Error) Current() error { @@ -178,53 +106,46 @@ func (err *Error) Next() error { return err.error } -// SetCode updates the internal code with given code. -func (err *Error) SetCode(code gcode.Code) { - if err == nil { - return - } - err.code = code +// Unwrap is alias of function `Next`. +// It is just for implements for stdlib errors.Unwrap from Go version 1.17. +func (err *Error) Unwrap() error { + return err.Next() } -// MarshalJSON implements the interface MarshalJSON for json.Marshal. -// Note that do not use pointer as its receiver here. -func (err Error) MarshalJSON() ([]byte, error) { - return []byte(`"` + err.Error() + `"`), nil +// Equal reports whether current error `err` equals to error `target`. +// Please note that, in default comparison for `Error`, +// the errors are considered the same if both the `code` and `text` of them are the same. +func (err *Error) Equal(target error) bool { + if err == target { + return true + } + // Code should be the same. + // Note that if both errors have `nil` code, they are also considered equal. + if err.code != Code(target) { + return false + } + // Text should be the same. + if err.text != fmt.Sprintf(`%-s`, target) { + return false + } + return true } -// formatSubStack formats the stack for error. -func formatSubStack(st stack, buffer *bytes.Buffer) { - if st == nil { - return +// Is reports whether current error `err` has error `target` in its chaining errors. +// It is just for implements for stdlib errors.Unwrap from Go version 1.17. +func (err *Error) Is(target error) bool { + if Equal(err, target) { + return true } - index := 1 - space := " " - for _, p := range st { - if fn := runtime.FuncForPC(p - 1); fn != nil { - file, line := fn.FileLine(p - 1) - // Custom filtering. - if strings.Contains(file, stackFilterKeyLocal) { - continue - } - // Avoid stack string like "`autogenerated`" - if strings.Contains(file, "<") { - continue - } - // Ignore GO ROOT paths. - if goRootForFilter != "" && - len(file) >= len(goRootForFilter) && - file[0:len(goRootForFilter)] == goRootForFilter { - continue - } - // Graceful indent. - if index > 9 { - space = " " - } - buffer.WriteString(fmt.Sprintf( - " %d).%s%s\n \t%s:%d\n", - index, space, fn.Name(), file, line, - )) - index++ - } + nextErr := err.Next() + if nextErr == nil { + return false } + if Equal(nextErr, target) { + return true + } + if e, ok := nextErr.(iIs); ok { + return e.Is(target) + } + return false } diff --git a/errors/gerror/gerror_error_code.go b/errors/gerror/gerror_error_code.go new file mode 100644 index 000000000..880cc54ff --- /dev/null +++ b/errors/gerror/gerror_error_code.go @@ -0,0 +1,31 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gerror + +import ( + "github.com/gogf/gf/v2/errors/gcode" +) + +// Code returns the error code. +// It returns CodeNil if it has no error code. +func (err *Error) Code() gcode.Code { + if err == nil { + return gcode.CodeNil + } + if err.code == gcode.CodeNil { + return Code(err.Next()) + } + return err.code +} + +// SetCode updates the internal code with given code. +func (err *Error) SetCode(code gcode.Code) { + if err == nil { + return + } + err.code = code +} diff --git a/errors/gerror/gerror_error_format.go b/errors/gerror/gerror_error_format.go new file mode 100644 index 000000000..e020c374e --- /dev/null +++ b/errors/gerror/gerror_error_format.go @@ -0,0 +1,80 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gerror + +import ( + "bytes" + "fmt" + "io" + "runtime" + "strings" +) + +// Format formats the frame according to the fmt.Formatter interface. +// +// %v, %s : Print all the error string; +// %-v, %-s : Print current level error string; +// %+s : Print full stack error list; +// %+v : Print the error string and full stack error list; +func (err *Error) Format(s fmt.State, verb rune) { + switch verb { + case 's', 'v': + switch { + case s.Flag('-'): + if err.text != "" { + _, _ = io.WriteString(s, err.text) + } else { + _, _ = io.WriteString(s, err.Error()) + } + case s.Flag('+'): + if verb == 's' { + _, _ = io.WriteString(s, err.Stack()) + } else { + _, _ = io.WriteString(s, err.Error()+"\n"+err.Stack()) + } + default: + _, _ = io.WriteString(s, err.Error()) + } + } +} + +// formatSubStack formats the stack for error. +func formatSubStack(st stack, buffer *bytes.Buffer) { + if st == nil { + return + } + index := 1 + space := " " + for _, p := range st { + if fn := runtime.FuncForPC(p - 1); fn != nil { + file, line := fn.FileLine(p - 1) + // Custom filtering. + if strings.Contains(file, stackFilterKeyLocal) { + continue + } + // Avoid stack string like "`autogenerated`" + if strings.Contains(file, "<") { + continue + } + // Ignore GO ROOT paths. + if goRootForFilter != "" && + len(file) >= len(goRootForFilter) && + file[0:len(goRootForFilter)] == goRootForFilter { + continue + } + // Graceful indent. + if index > 9 { + space = " " + } + buffer.WriteString(fmt.Sprintf( + " %d).%s%s\n \t%s:%d\n", + index, space, fn.Name(), file, line, + )) + index++ + } + } +} diff --git a/errors/gerror/gerror_error_json.go b/errors/gerror/gerror_error_json.go new file mode 100644 index 000000000..5c290d7af --- /dev/null +++ b/errors/gerror/gerror_error_json.go @@ -0,0 +1,13 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gerror + +// MarshalJSON implements the interface MarshalJSON for json.Marshal. +// Note that do not use pointer as its receiver here. +func (err Error) MarshalJSON() ([]byte, error) { + return []byte(`"` + err.Error() + `"`), nil +} diff --git a/errors/gerror/gerror_error_stack.go b/errors/gerror/gerror_error_stack.go new file mode 100644 index 000000000..72769ca23 --- /dev/null +++ b/errors/gerror/gerror_error_stack.go @@ -0,0 +1,42 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gerror + +import ( + "bytes" + "fmt" +) + +// Stack returns the stack callers as string. +// It returns an empty string if the `err` does not support stacks. +func (err *Error) Stack() string { + if err == nil { + return "" + } + var ( + loop = err + index = 1 + buffer = bytes.NewBuffer(nil) + ) + for loop != nil { + buffer.WriteString(fmt.Sprintf("%d. %-v\n", index, loop)) + index++ + formatSubStack(loop.stack, buffer) + if loop.error != nil { + if e, ok := loop.error.(*Error); ok { + loop = e + } else { + buffer.WriteString(fmt.Sprintf("%d. %s\n", index, loop.error.Error())) + index++ + break + } + } else { + break + } + } + return buffer.String() +} diff --git a/errors/gerror/gerror_interface.go b/errors/gerror/gerror_interface.go new file mode 100644 index 000000000..317f951dc --- /dev/null +++ b/errors/gerror/gerror_interface.go @@ -0,0 +1,51 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gerror + +import ( + "github.com/gogf/gf/v2/errors/gcode" +) + +// iIs is the interface for Is feature. +type iIs interface { + Is(target error) bool +} + +// iEqual is the interface for Equal feature. +type iEqual interface { + Equal(target error) bool +} + +// iCode is the interface for Code feature. +type iCode interface { + Error() string + Code() gcode.Code +} + +// iStack is the interface for Stack feature. +type iStack interface { + Error() string + Stack() string +} + +// iCause is the interface for Cause feature. +type iCause interface { + Error() string + Cause() error +} + +// iCurrent is the interface for Current feature. +type iCurrent interface { + Error() string + Current() error +} + +// iNext is the interface for Next feature. +type iNext interface { + Error() string + Next() error +} diff --git a/errors/gerror/gerror_option.go b/errors/gerror/gerror_option.go index c2ff71152..33ed881f7 100644 --- a/errors/gerror/gerror_option.go +++ b/errors/gerror/gerror_option.go @@ -16,7 +16,7 @@ type Option struct { Code gcode.Code // Error code if necessary. } -// NewOption creates and returns an error with Option. +// NewOption creates and returns a custom error with Option. // It is the senior usage for creating error, which is often used internally in framework. func NewOption(option Option) error { err := &Error{ diff --git a/errors/gerror/gerror_z_unit_test.go b/errors/gerror/gerror_z_unit_test.go index 228cb91df..c8c0ad719 100644 --- a/errors/gerror/gerror_z_unit_test.go +++ b/errors/gerror/gerror_z_unit_test.go @@ -255,6 +255,24 @@ func Test_Next(t *testing.T) { }) } +func Test_Unwrap(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err := errors.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") + t.Assert(err.Error(), "3: 2: 1") + + err = gerror.Unwrap(err) + t.Assert(err.Error(), "2: 1") + + err = gerror.Unwrap(err) + t.Assert(err.Error(), "1") + + err = gerror.Unwrap(err) + t.AssertNil(err) + }) +} + func Test_Code(t *testing.T) { gtest.C(t, func(t *gtest.T) { err := errors.New("123") @@ -340,3 +358,26 @@ func Test_HasStack(t *testing.T) { t.Assert(gerror.HasStack(err2), true) }) } + +func Test_Equal(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err1 := errors.New("1") + err2 := errors.New("1") + err3 := gerror.New("1") + err4 := gerror.New("4") + t.Assert(gerror.Equal(err1, err2), false) + t.Assert(gerror.Equal(err1, err3), true) + t.Assert(gerror.Equal(err2, err3), true) + t.Assert(gerror.Equal(err3, err4), false) + t.Assert(gerror.Equal(err1, err4), false) + }) +} + +func Test_Is(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err1 := errors.New("1") + err2 := gerror.Wrap(err1, "2") + err2 = gerror.Wrap(err2, "3") + t.Assert(gerror.Is(err2, err1), true) + }) +} diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index a7b33f9cc..1f68a9259 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -152,9 +152,10 @@ func (r *Request) Get(key string, def ...interface{}) *gvar.Var { func (r *Request) GetBody() []byte { if r.bodyContent == nil { var err error - r.bodyContent, err = ioutil.ReadAll(r.Body) - if err != nil { - panic(gerror.WrapCode(gcode.CodeInternalError, err, `ReadAll from body failed`)) + if r.bodyContent, err = ioutil.ReadAll(r.Body); err != nil { + errMsg := `Read from request Body failed` + errMsg += `, the Body might be closed or read manually from middleware/hook/other package previously` + panic(gerror.WrapCode(gcode.CodeInternalError, err, errMsg)) } r.Body = utils.NewReadCloser(r.bodyContent, true) }