improve package gerror, add HasCode/HasError function for package gerror (#2006)

This commit is contained in:
John Guo
2022-07-15 10:49:04 +08:00
committed by GitHub
parent 2c70bb6a00
commit 5d51e9fa2c
17 changed files with 512 additions and 424 deletions

View File

@ -4,316 +4,55 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package gerror provides simple functions to manipulate errors.
// Package gerror provides rich functionalities to manipulate errors.
//
// Very note that, this package is quite a basic package, which SHOULD NOT import extra packages
// For maintainers, please very note that,
// this package is quite a basic package, which SHOULD NOT import extra packages
// except standard packages and internal packages, to avoid cycle imports.
package gerror
import (
"fmt"
"strings"
"github.com/gogf/gf/v2/errors/gcode"
)
// New creates and returns an error which is formatted from given text.
func New(text string) error {
return &Error{
stack: callers(),
text: text,
code: gcode.CodeNil,
}
// IIs is the interface for Is feature.
type IIs interface {
Error() string
Is(target error) bool
}
// Newf returns an error that formats as the given format and args.
func Newf(format string, args ...interface{}) error {
return &Error{
stack: callers(),
text: fmt.Sprintf(format, args...),
code: gcode.CodeNil,
}
// IEqual is the interface for Equal feature.
type IEqual interface {
Error() string
Equal(target error) bool
}
// NewSkip creates and returns an error which is formatted from given text.
// The parameter `skip` specifies the stack callers skipped amount.
func NewSkip(skip int, text string) error {
return &Error{
stack: callers(skip),
text: text,
code: gcode.CodeNil,
}
// ICode is the interface for Code feature.
type ICode interface {
Error() string
Code() gcode.Code
}
// NewSkipf returns an error that formats as the given format and args.
// The parameter `skip` specifies the stack callers skipped amount.
func NewSkipf(skip int, format string, args ...interface{}) error {
return &Error{
stack: callers(skip),
text: fmt.Sprintf(format, args...),
code: gcode.CodeNil,
}
// IStack is the interface for Stack feature.
type IStack interface {
Error() string
Stack() string
}
// Wrap wraps error with text. It returns nil if given err is nil.
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
func Wrap(err error, text string) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(),
text: text,
code: Code(err),
}
// ICause is the interface for Cause feature.
type ICause interface {
Error() string
Cause() error
}
// Wrapf returns an error annotating err with a stack trace at the point Wrapf is called, and the format specifier.
// It returns nil if given `err` is nil.
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(),
text: fmt.Sprintf(format, args...),
code: Code(err),
}
// ICurrent is the interface for Current feature.
type ICurrent interface {
Error() string
Current() error
}
// WrapSkip wraps error with text. It returns nil if given err is nil.
// The parameter `skip` specifies the stack callers skipped amount.
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
func WrapSkip(skip int, err error, text string) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(skip),
text: text,
code: Code(err),
}
}
// WrapSkipf wraps error with text that is formatted with given format and args. It returns nil if given err is nil.
// The parameter `skip` specifies the stack callers skipped amount.
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
func WrapSkipf(skip int, err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(skip),
text: fmt.Sprintf(format, args...),
code: Code(err),
}
}
// NewCode creates and returns an error that has error code and given text.
func NewCode(code gcode.Code, text ...string) error {
return &Error{
stack: callers(),
text: strings.Join(text, ", "),
code: code,
}
}
// NewCodef returns an error that has error code and formats as the given format and args.
func NewCodef(code gcode.Code, format string, args ...interface{}) error {
return &Error{
stack: callers(),
text: fmt.Sprintf(format, args...),
code: code,
}
}
// NewCodeSkip creates and returns an error which has error code and is formatted from given text.
// The parameter `skip` specifies the stack callers skipped amount.
func NewCodeSkip(code gcode.Code, skip int, text ...string) error {
return &Error{
stack: callers(skip),
text: strings.Join(text, ", "),
code: code,
}
}
// NewCodeSkipf returns an error that has error code and formats as the given format and args.
// The parameter `skip` specifies the stack callers skipped amount.
func NewCodeSkipf(code gcode.Code, skip int, format string, args ...interface{}) error {
return &Error{
stack: callers(skip),
text: fmt.Sprintf(format, args...),
code: code,
}
}
// WrapCode wraps error with code and text.
// It returns nil if given err is nil.
func WrapCode(code gcode.Code, err error, text ...string) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(),
text: strings.Join(text, ", "),
code: code,
}
}
// WrapCodef wraps error with code and format specifier.
// It returns nil if given `err` is nil.
func WrapCodef(code gcode.Code, err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(),
text: fmt.Sprintf(format, args...),
code: code,
}
}
// WrapCodeSkip wraps error with code and text.
// It returns nil if given err is nil.
// The parameter `skip` specifies the stack callers skipped amount.
func WrapCodeSkip(code gcode.Code, skip int, err error, text ...string) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(skip),
text: strings.Join(text, ", "),
code: code,
}
}
// WrapCodeSkipf wraps error with code and text that is formatted with given format and args.
// It returns nil if given err is nil.
// The parameter `skip` specifies the stack callers skipped amount.
func WrapCodeSkipf(code gcode.Code, skip int, err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(skip),
text: fmt.Sprintf(format, args...),
code: code,
}
}
// Code returns the error code of current error.
// It returns CodeNil if it has no error code neither it does not implement interface Code.
func Code(err error) gcode.Code {
if err == nil {
return gcode.CodeNil
}
if e, ok := err.(iCode); ok {
return e.Code()
}
if e, ok := err.(iNext); ok {
return Code(e.Next())
}
if e, ok := err.(iUnwrap); ok {
return Code(e.Unwrap())
}
return gcode.CodeNil
}
// Cause returns the root cause error of `err`.
func Cause(err error) error {
if err == nil {
return nil
}
if e, ok := err.(iCause); ok {
return e.Cause()
}
if e, ok := err.(iNext); ok {
return Cause(e.Next())
}
if e, ok := err.(iUnwrap); ok {
return Cause(e.Unwrap())
}
return err
}
// Stack returns the stack callers as string.
// It returns the error string directly if the `err` does not support stacks.
func Stack(err error) string {
if err == nil {
return ""
}
if e, ok := err.(iStack); ok {
return e.Stack()
}
return err.Error()
}
// Current creates and returns the current level error.
// It returns nil if current level error is nil.
func Current(err error) error {
if err == nil {
return nil
}
if e, ok := err.(iCurrent); ok {
return e.Current()
}
return err
}
// Next returns the next level error.
// It returns nil if current level error or the next level error is nil.
func Next(err error) error {
if err == nil {
return nil
}
if e, ok := err.(iNext); ok {
return e.Next()
}
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
// IUnwrap is the interface for Unwrap feature.
type IUnwrap interface {
Error() string
Unwrap() error
}

110
errors/gerror/gerror_api.go Normal file
View File

@ -0,0 +1,110 @@
// 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 (
"fmt"
"github.com/gogf/gf/v2/errors/gcode"
)
// New creates and returns an error which is formatted from given text.
func New(text string) error {
return &Error{
stack: callers(),
text: text,
code: gcode.CodeNil,
}
}
// Newf returns an error that formats as the given format and args.
func Newf(format string, args ...interface{}) error {
return &Error{
stack: callers(),
text: fmt.Sprintf(format, args...),
code: gcode.CodeNil,
}
}
// NewSkip creates and returns an error which is formatted from given text.
// The parameter `skip` specifies the stack callers skipped amount.
func NewSkip(skip int, text string) error {
return &Error{
stack: callers(skip),
text: text,
code: gcode.CodeNil,
}
}
// NewSkipf returns an error that formats as the given format and args.
// The parameter `skip` specifies the stack callers skipped amount.
func NewSkipf(skip int, format string, args ...interface{}) error {
return &Error{
stack: callers(skip),
text: fmt.Sprintf(format, args...),
code: gcode.CodeNil,
}
}
// Wrap wraps error with text. It returns nil if given err is nil.
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
func Wrap(err error, text string) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(),
text: text,
code: Code(err),
}
}
// Wrapf returns an error annotating err with a stack trace at the point Wrapf is called, and the format specifier.
// It returns nil if given `err` is nil.
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(),
text: fmt.Sprintf(format, args...),
code: Code(err),
}
}
// WrapSkip wraps error with text. It returns nil if given err is nil.
// The parameter `skip` specifies the stack callers skipped amount.
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
func WrapSkip(skip int, err error, text string) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(skip),
text: text,
code: Code(err),
}
}
// WrapSkipf wraps error with text that is formatted with given format and args. It returns nil if given err is nil.
// The parameter `skip` specifies the stack callers skipped amount.
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
func WrapSkipf(skip int, err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(skip),
text: fmt.Sprintf(format, args...),
code: Code(err),
}
}

View File

@ -0,0 +1,139 @@
// 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 (
"fmt"
"strings"
"github.com/gogf/gf/v2/errors/gcode"
)
// NewCode creates and returns an error that has error code and given text.
func NewCode(code gcode.Code, text ...string) error {
return &Error{
stack: callers(),
text: strings.Join(text, ", "),
code: code,
}
}
// NewCodef returns an error that has error code and formats as the given format and args.
func NewCodef(code gcode.Code, format string, args ...interface{}) error {
return &Error{
stack: callers(),
text: fmt.Sprintf(format, args...),
code: code,
}
}
// NewCodeSkip creates and returns an error which has error code and is formatted from given text.
// The parameter `skip` specifies the stack callers skipped amount.
func NewCodeSkip(code gcode.Code, skip int, text ...string) error {
return &Error{
stack: callers(skip),
text: strings.Join(text, ", "),
code: code,
}
}
// NewCodeSkipf returns an error that has error code and formats as the given format and args.
// The parameter `skip` specifies the stack callers skipped amount.
func NewCodeSkipf(code gcode.Code, skip int, format string, args ...interface{}) error {
return &Error{
stack: callers(skip),
text: fmt.Sprintf(format, args...),
code: code,
}
}
// WrapCode wraps error with code and text.
// It returns nil if given err is nil.
func WrapCode(code gcode.Code, err error, text ...string) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(),
text: strings.Join(text, ", "),
code: code,
}
}
// WrapCodef wraps error with code and format specifier.
// It returns nil if given `err` is nil.
func WrapCodef(code gcode.Code, err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(),
text: fmt.Sprintf(format, args...),
code: code,
}
}
// WrapCodeSkip wraps error with code and text.
// It returns nil if given err is nil.
// The parameter `skip` specifies the stack callers skipped amount.
func WrapCodeSkip(code gcode.Code, skip int, err error, text ...string) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(skip),
text: strings.Join(text, ", "),
code: code,
}
}
// WrapCodeSkipf wraps error with code and text that is formatted with given format and args.
// It returns nil if given err is nil.
// The parameter `skip` specifies the stack callers skipped amount.
func WrapCodeSkipf(code gcode.Code, skip int, err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(skip),
text: fmt.Sprintf(format, args...),
code: code,
}
}
// Code returns the error code of current error.
// It returns `CodeNil` if it has no error code neither it does not implement interface Code.
func Code(err error) gcode.Code {
if err == nil {
return gcode.CodeNil
}
if e, ok := err.(ICode); ok {
return e.Code()
}
if e, ok := err.(IUnwrap); ok {
return Code(e.Unwrap())
}
return gcode.CodeNil
}
// HasCode checks and reports whether `err` has `code` in its chaining errors.
func HasCode(err error, code gcode.Code) bool {
if err == nil {
return false
}
if e, ok := err.(ICode); ok {
return code == e.Code()
}
if e, ok := err.(IUnwrap); ok {
return HasCode(e.Unwrap(), code)
}
return false
}

View File

@ -0,0 +1,118 @@
// 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 (
"runtime"
)
// stack represents a stack of program counters.
type stack []uintptr
const (
// maxStackDepth marks the max stack depth for error back traces.
maxStackDepth = 32
)
// Cause returns the root cause error of `err`.
func Cause(err error) error {
if err == nil {
return nil
}
if e, ok := err.(ICause); ok {
return e.Cause()
}
if e, ok := err.(IUnwrap); ok {
return Cause(e.Unwrap())
}
return err
}
// Stack returns the stack callers as string.
// It returns the error string directly if the `err` does not support stacks.
func Stack(err error) string {
if err == nil {
return ""
}
if e, ok := err.(IStack); ok {
return e.Stack()
}
return err.Error()
}
// Current creates and returns the current level error.
// It returns nil if current level error is nil.
func Current(err error) error {
if err == nil {
return nil
}
if e, ok := err.(ICurrent); ok {
return e.Current()
}
return err
}
// Unwrap returns the next level error.
// It returns nil if current level error or the next level error is nil.
func Unwrap(err error) error {
if err == nil {
return nil
}
if e, ok := err.(IUnwrap); ok {
return e.Unwrap()
}
return nil
}
// HasStack checks and reports whether `err` implemented interface `gerror.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 logic 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.Is from Go version 1.17.
func Is(err, target error) bool {
if e, ok := err.(IIs); ok {
return e.Is(target)
}
return false
}
// HasError is alias of Is, which more easily understanding semantics.
func HasError(err, target error) bool {
return Is(err, target)
}
// callers returns the stack callers.
// Note that it here just retrieves the caller memory address array not the caller information.
func callers(skip ...int) stack {
var (
pcs [maxStackDepth]uintptr
n = 3
)
if len(skip) > 0 {
n += skip[0]
}
return pcs[:runtime.Callers(n, pcs[:])]
}

View File

@ -68,7 +68,7 @@ func (err *Error) Cause() error {
if e, ok := loop.error.(*Error); ok {
// Internal Error struct.
loop = e
} else if e, ok := loop.error.(iCause); ok {
} else if e, ok := loop.error.(ICause); ok {
// Other Error that implements ApiCause interface.
return e.Cause()
} else {
@ -76,6 +76,7 @@ func (err *Error) Cause() error {
}
} else {
// return loop
//
// To be compatible with Case of https://github.com/pkg/errors.
return errors.New(loop.text)
}
@ -97,21 +98,15 @@ func (err *Error) Current() error {
}
}
// Next returns the next level error.
// It returns nil if current level error or the next level error is nil.
func (err *Error) Next() error {
// 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 {
if err == nil {
return nil
}
return err.error
}
// 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()
}
// 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.
@ -132,19 +127,19 @@ func (err *Error) Equal(target error) bool {
}
// 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.
// It is just for implements for stdlib errors.Is from Go version 1.17.
func (err *Error) Is(target error) bool {
if Equal(err, target) {
return true
}
nextErr := err.Next()
nextErr := err.Unwrap()
if nextErr == nil {
return false
}
if Equal(nextErr, target) {
return true
}
if e, ok := nextErr.(iIs); ok {
if e, ok := nextErr.(IIs); ok {
return e.Is(target)
}
return false

View File

@ -17,7 +17,7 @@ func (err *Error) Code() gcode.Code {
return gcode.CodeNil
}
if err.code == gcode.CodeNil {
return Code(err.Next())
return Code(err.Unwrap())
}
return err.code
}

View File

@ -1,57 +0,0 @@
// 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
}
// iUnwrap is the interface for Unwrap feature.
type iUnwrap interface {
Error() string
Unwrap() error
}

View File

@ -1,30 +0,0 @@
// 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 "runtime"
// stack represents a stack of program counters.
type stack []uintptr
const (
// maxStackDepth marks the max stack depth for error back traces.
maxStackDepth = 32
)
// callers returns the stack callers.
// Note that it here just retrieves the caller memory address array not the caller information.
func callers(skip ...int) stack {
var (
pcs [maxStackDepth]uintptr
n = 3
)
if len(skip) > 0 {
n += skip[0]
}
return pcs[:runtime.Callers(n, pcs[:])]
}

View File

@ -237,24 +237,6 @@ func Test_Current(t *testing.T) {
})
}
func Test_Next(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.Next(err)
t.Assert(err.Error(), "2: 1")
err = gerror.Next(err)
t.Assert(err.Error(), "1")
err = gerror.Next(err)
t.AssertNil(err)
})
}
func Test_Unwrap(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
err := errors.New("1")
@ -381,3 +363,25 @@ func Test_Is(t *testing.T) {
t.Assert(gerror.Is(err2, err1), true)
})
}
func Test_HashError(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.HasError(err2, err1), true)
})
}
func Test_HashCode(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
err1 := errors.New("1")
err2 := gerror.WrapCode(gcode.CodeNotAuthorized, err1, "2")
err3 := gerror.Wrap(err2, "3")
err4 := gerror.Wrap(err3, "4")
t.Assert(gerror.HasCode(err1, gcode.CodeNotAuthorized), false)
t.Assert(gerror.HasCode(err2, gcode.CodeNotAuthorized), true)
t.Assert(gerror.HasCode(err3, gcode.CodeNotAuthorized), true)
t.Assert(gerror.HasCode(err4, gcode.CodeNotAuthorized), true)
})
}