Skip to content

Commit

Permalink
couple of improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
hedzr committed Dec 13, 2023
1 parent bee5d98 commit e0fb285
Show file tree
Hide file tree
Showing 19 changed files with 520 additions and 314 deletions.
3 changes: 0 additions & 3 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# CHANGELOG

- v3.1.11
- fixed error.Is to avoid panic when testing on some obj

- v3.1.9
- fixed error.Is deep test to check two errors' message text contents if matched
- fixed errors.v3.Join when msg is not empty in an err obj
Expand Down
179 changes: 146 additions & 33 deletions asisunwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
// As will panic if target is not a non-nil pointer to either a
// type that implements error, or to any interface type. "As"
// returns false if err is nil.
func As(err error, target interface{}) bool {
func As(err error, target interface{}) bool { //nolint:revive
if target == nil {
panic("errors: target cannot be nil")
}
Expand All @@ -37,20 +37,20 @@ func As(err error, target interface{}) bool {
// }
targetType := typ.Elem()
for err != nil {
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { //nolint:revive
return true
}
if reflect.TypeOf(err).AssignableTo(targetType) {
val.Elem().Set(reflect.ValueOf(err))
return true
}
err = Unwrap(err)
err = Unwrap(err) //nolint:revive
}
return false
}

// AsSlice tests err.As for errs slice
func AsSlice(errs []error, target interface{}) bool {
func AsSlice(errs []error, target interface{}) bool { //nolint:revive
if target == nil {
panic("errors: target cannot be nil")
}
Expand All @@ -69,7 +69,7 @@ func AsSlice(errs []error, target interface{}) bool {
val.Elem().Set(reflect.ValueOf(err))
return true
}
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { //nolint:revive
return true
}
err = Unwrap(err) //nolint:ineffassign,staticcheck
Expand Down Expand Up @@ -97,49 +97,144 @@ func IsAnyOf(err error, targets ...error) bool {
// An error is considered to match a target if it is equal to that
// target or if it implements a method Is(error) bool such that
// Is(target) returns true.
func Is(err, target error) bool {
func Is(err, target error) bool { //nolint:revive
if target == nil {
return err == target
return err == nil
}

isComparable := reflect.TypeOf(target).Comparable()
tv := reflect.ValueOf(target)
// target is not Code-based, try convert source err with target's type, and test whether its plain text message is equal
var savedMsg string
if !isNil(tv) {
savedMsg = safeErrorGetMsg(target)
savedMsg = target.Error()
}
for {
if isComparable {
if err == target {
return true
}
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
if _, ok := target.(Code); !ok {
if ok = As(err, &target); ok && !isNil(reflect.ValueOf(target)) && strings.EqualFold(safeErrorGetMsg(target), savedMsg) {
var te Code
if ok = As(err, &te); ok && !isNil(reflect.ValueOf(err)) && strings.EqualFold(te.Error(), savedMsg) {
return true
}
}
// TODO: consider supporting target.Is(err). This would allow
// user-definable predicates, but also may allow for coping with sloppy
// APIs, thereby making it easier to get away with them.
if err = Unwrap(err); err == nil {

// // TODO: consider supporting target.Is(err). This would allow
// // user-definable predicates, but also may allow for coping with sloppy
// // APIs, thereby making it easier to get away with them.
// if err = Unwrap(err); err == nil {
// errors.Is()
// return false
// }

switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap() //nolint:revive
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if Is(err, target) {
return true
}
}
return false
default:
return false
}
}
}

func IsStd(err, target error) bool { //nolint:revive
if target == nil {
return err == target
}

isComparable := reflect.TypeOf(target).Comparable()
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap() //nolint:revive
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if Is(err, target) {
return true
}
}
return false
default:
return false
}
}
}

func safeErrorGetMsg(err error) (msg string) {
if err != nil {
defer func() {
if e := recover(); e != nil {
fmt.Printf("some error recovered: %v", e)
func Iss(err error, targets ...error) (matched bool) { //nolint:revive
if targets == nil {
return err == nil
}
if err == nil {
return true
}

for _, target := range targets {
isComparable := reflect.TypeOf(target).Comparable()
tv := reflect.ValueOf(target)
// target is not Code-based, try convert source err with target's type, and test whether its plain text message is equal
var savedMsg string
if !isNil(tv) {
savedMsg = target.Error()
}
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
if _, ok := target.(Code); !ok {
if ok = As(err, &target); ok && !isNil(reflect.ValueOf(target)) && strings.EqualFold(target.Error(), savedMsg) {
return true
}
}

// // TODO: consider supporting target.Is(err). This would allow
// // user-definable predicates, but also may allow for coping with sloppy
// // APIs, thereby making it easier to get away with them.
// if err = Unwrap(err); err == nil {
// return false
// }

switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap() //nolint:revive
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if Is(err, target) {
return true
}
}
return false
default:
return false
}
}()
msg = err.Error()
}
}
return
}
Expand Down Expand Up @@ -175,7 +270,7 @@ func isNilv(v *reflect.Value) bool {
}

// IsSlice tests err.Is for errs slice
func IsSlice(errs []error, target error) bool {
func IsSlice(errs []error, target error) bool { //nolint:revive
if target == nil {
// for _, e := range errs {
// if e == target {
Expand Down Expand Up @@ -215,7 +310,7 @@ func IsSlice(errs []error, target error) bool {
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
func TypeIs(err, target error) bool {
func TypeIs(err, target error) bool { //nolint:revive
if target == nil {
return err == target
}
Expand All @@ -230,17 +325,35 @@ func TypeIs(err, target error) bool {
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
// TODO: consider supporting target.Is(err). This would allow
// user-definable predicates, but also may allow for coping with sloppy
// APIs, thereby making it easier to get away with them.
if err = Unwrap(err); err == nil {

// // TODO: consider supporting target.Is(err). This would allow
// // user-definable predicates, but also may allow for coping with sloppy
// // APIs, thereby making it easier to get away with them.
// if err = Unwrap(err); err == nil {
// return false
// }

switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap() //nolint:revive
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if Is(err, target) {
return true
}
}
return false
default:
return false
}
}
}

// TypeIsSlice tests err.Is for errs slice
func TypeIsSlice(errs []error, target error) bool {
func TypeIsSlice(errs []error, target error) bool { //nolint:revive
if target == nil {
// for _, e := range errs {
// if e == target {
Expand Down Expand Up @@ -313,13 +426,13 @@ func Unwrap(err error) error {
// Wrap returns an error annotating err with a Stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string, args ...interface{}) *WithStackInfo {
func Wrap(err error, message string, args ...interface{}) *WithStackInfo { //nolint:revive
if err == nil {
return nil
}

if len(args) > 0 {
message = fmt.Sprintf(message, args...)
message = fmt.Sprintf(message, args...) //nolint:revive
}

return &WithStackInfo{
Expand Down
Loading

0 comments on commit e0fb285

Please sign in to comment.