mirror of
https://github.com/kataras/iris.git
synced 2026-01-10 05:25:58 +00:00
Upgrade to new go errors and some minor fixes and improvements including easier debugging of invalid routes and e.t.c.
Former-commit-id: 5809157b952ccc61a67a9861470774b3a6fee024
This commit is contained in:
356
core/errgroup/errgroup.go
Normal file
356
core/errgroup/errgroup.go
Normal file
@@ -0,0 +1,356 @@
|
||||
package errgroup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Check reports whether the "err" is not nil.
|
||||
// If it is a group then it returns true if that or its children contains any error.
|
||||
func Check(err error) error {
|
||||
if isNotNil(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walk loops through each of the errors of "err".
|
||||
// If "err" is *Group then it fires the "visitor" for each of its errors, including children.
|
||||
// if "err" is *Error then it fires the "visitor" with its type and wrapped error.
|
||||
// Otherwise it fires the "visitor" once with typ of nil and err as "err".
|
||||
func Walk(err error, visitor func(typ interface{}, err error)) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if group, ok := err.(*Group); ok {
|
||||
list := group.getAllErrors()
|
||||
for _, entry := range list {
|
||||
if e, ok := entry.(*Error); ok {
|
||||
visitor(e.Type, e.Err) // e.Unwrap() <-no.
|
||||
} else {
|
||||
visitor(nil, err)
|
||||
}
|
||||
}
|
||||
} else if e, ok := err.(*Error); ok {
|
||||
visitor(e.Type, e.Err)
|
||||
} else {
|
||||
visitor(nil, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
func Errors(err error, conv bool) []error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if group, ok := err.(*Group); ok {
|
||||
list := group.getAllErrors()
|
||||
if conv {
|
||||
for i, entry := range list {
|
||||
if _, ok := entry.(*Error); !ok {
|
||||
list[i] = &Error{Err: entry, Type: group.Type}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
func Type(err error) interface{} {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if e, ok := err.(*Error); ok && e.Err != nil {
|
||||
return e.Type
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Fill(parent *Group, errors []*Error) {
|
||||
for _, err := range errors {
|
||||
if err.Type == parent.Type {
|
||||
parent.Add(err)
|
||||
continue
|
||||
}
|
||||
|
||||
parent.Group(err.Type).Err(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
// Error implements the error interface.
|
||||
// It is a special error type which keep the "Type" of the
|
||||
// Group that it's created through Group's `Err` and `Errf` methods.
|
||||
type Error struct {
|
||||
Err error `json:"error" xml:"Error" yaml:"Error" toml:"Error" sql:"error"`
|
||||
Type interface{} `json:"type" xml:"Type" yaml:"Type" toml:"Type" sql:"type"`
|
||||
}
|
||||
|
||||
// Error returns the error message of the "Err".
|
||||
func (e *Error) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Unwrap calls and returns the result of the "Err" Unwrap method or nil.
|
||||
func (e *Error) Unwrap() error {
|
||||
return errors.Unwrap(e.Err)
|
||||
}
|
||||
|
||||
// Is reports whether the "err" is an *Error.
|
||||
func (e *Error) Is(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ok := errors.Is(e.Err, err)
|
||||
if !ok {
|
||||
te, ok := err.(*Error)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return errors.Is(e.Err, te.Err)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// As reports whether the "target" can be used as &Error{target.Type: ?}.
|
||||
func (e *Error) As(target interface{}) bool {
|
||||
if target == nil {
|
||||
return target == e
|
||||
}
|
||||
|
||||
ok := errors.As(e.Err, target)
|
||||
if !ok {
|
||||
te, ok := target.(*Error)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if te.Type != nil {
|
||||
if te.Type != e.Type {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return errors.As(e.Err, &te.Err)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// Group is an error container of a specific Type and can have child containers per type too.
|
||||
type Group struct {
|
||||
parent *Group
|
||||
// a list of children groups, used to get or create new group through Group method.
|
||||
children map[interface{}]*Group
|
||||
depth int
|
||||
|
||||
Type interface{}
|
||||
Errors []error // []*Error
|
||||
|
||||
// if true then this Group's Error method will return the messages of the errors made by this Group's Group method.
|
||||
// Defaults to true.
|
||||
IncludeChildren bool // it clones.
|
||||
// IncludeTypeText bool
|
||||
index int // group index.
|
||||
}
|
||||
|
||||
// New returns a new empty Group.
|
||||
func New(typ interface{}) *Group {
|
||||
return &Group{
|
||||
Type: typ,
|
||||
IncludeChildren: true,
|
||||
}
|
||||
}
|
||||
|
||||
const delim = "\n"
|
||||
|
||||
func (g *Group) Error() (s string) {
|
||||
if len(g.Errors) > 0 {
|
||||
msgs := make([]string, len(g.Errors), len(g.Errors))
|
||||
for i, err := range g.Errors {
|
||||
msgs[i] = err.Error()
|
||||
}
|
||||
|
||||
s = strings.Join(msgs, delim)
|
||||
}
|
||||
|
||||
if g.IncludeChildren && len(g.children) > 0 {
|
||||
// return with order of definition.
|
||||
groups := g.getAllChildren()
|
||||
sortGroups(groups)
|
||||
|
||||
for _, ge := range groups {
|
||||
for _, childErr := range ge.Errors {
|
||||
s += childErr.Error() + delim
|
||||
}
|
||||
}
|
||||
|
||||
if s != "" {
|
||||
return s[:len(s)-1]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (g *Group) getAllErrors() []error {
|
||||
list := g.Errors[:]
|
||||
|
||||
if len(g.children) > 0 {
|
||||
// return with order of definition.
|
||||
groups := g.getAllChildren()
|
||||
sortGroups(groups)
|
||||
|
||||
for _, ge := range groups {
|
||||
list = append(list, ge.Errors...)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (g *Group) getAllChildren() []*Group {
|
||||
if len(g.children) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var groups []*Group
|
||||
for _, child := range g.children {
|
||||
groups = append(groups, append([]*Group{child}, child.getAllChildren()...)...)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
// Unwrap implements the dynamic std errors interface and it returns the parent Group.
|
||||
func (g *Group) Unwrap() error {
|
||||
return g.parent
|
||||
}
|
||||
|
||||
// Group creates a new group of "typ" type, if does not exist, and returns it.
|
||||
func (g *Group) Group(typ interface{}) *Group {
|
||||
if g.children == nil {
|
||||
g.children = make(map[interface{}]*Group)
|
||||
} else {
|
||||
for _, child := range g.children {
|
||||
if child.Type == typ {
|
||||
return child
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
child := &Group{
|
||||
Type: typ,
|
||||
parent: g,
|
||||
depth: g.depth + 1,
|
||||
IncludeChildren: g.IncludeChildren,
|
||||
index: g.index + 1 + len(g.children),
|
||||
}
|
||||
|
||||
g.children[typ] = child
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
// Add adds an error to the group.
|
||||
func (g *Group) Add(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
g.Errors = append(g.Errors, err)
|
||||
}
|
||||
|
||||
// Addf adds an error to the group like `fmt.Errorf` and returns it.
|
||||
func (g *Group) Addf(format string, args ...interface{}) error {
|
||||
err := fmt.Errorf(format, args...)
|
||||
g.Add(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Err adds an error the the group, it transforms it to an Error type if necessary and returns it.
|
||||
func (g *Group) Err(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e, ok := err.(*Error)
|
||||
if !ok {
|
||||
if ge, ok := err.(*Group); ok {
|
||||
if g.children == nil {
|
||||
g.children = make(map[interface{}]*Group)
|
||||
}
|
||||
|
||||
g.children[ge.Type] = ge
|
||||
return ge
|
||||
}
|
||||
|
||||
e = &Error{err, 0}
|
||||
}
|
||||
e.Type = g.Type
|
||||
|
||||
g.Add(e)
|
||||
return e
|
||||
}
|
||||
|
||||
// Errf adds an error like `fmt.Errorf` and returns it.
|
||||
func (g *Group) Errf(format string, args ...interface{}) error {
|
||||
return g.Err(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
func sortGroups(groups []*Group) {
|
||||
sort.Slice(groups, func(i, j int) bool {
|
||||
return groups[i].index < groups[j].index
|
||||
})
|
||||
}
|
||||
|
||||
func tryGetTypeText(typ interface{}) string {
|
||||
if typ == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch v := typ.(type) {
|
||||
case string:
|
||||
return v
|
||||
case fmt.Stringer:
|
||||
return v.String()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func isNotNil(err error) bool {
|
||||
if g, ok := err.(*Group); ok {
|
||||
if len(g.Errors) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(g.children) > 0 {
|
||||
for _, child := range g.children {
|
||||
if isNotNil(child) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return err != nil
|
||||
}
|
||||
173
core/errgroup/errgroup_test.go
Normal file
173
core/errgroup/errgroup_test.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package errgroup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestErrorError(t *testing.T) {
|
||||
testErr := errors.New("error")
|
||||
err := &Error{Err: testErr}
|
||||
if expected, got := testErr.Error(), err.Error(); expected != got {
|
||||
t.Fatalf("expected %s but got %s", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorUnwrap(t *testing.T) {
|
||||
wrapped := errors.New("unwrap")
|
||||
|
||||
err := &Error{Err: fmt.Errorf("this wraps:%w", wrapped)}
|
||||
if expected, got := wrapped, errors.Unwrap(err); expected != got {
|
||||
t.Fatalf("expected %#+v but got %#+v", expected, got)
|
||||
}
|
||||
|
||||
}
|
||||
func TestErrorIs(t *testing.T) {
|
||||
testErr := errors.New("is")
|
||||
err := &Error{Err: fmt.Errorf("this is: %w", testErr)}
|
||||
if expected, got := true, errors.Is(err, testErr); expected != got {
|
||||
t.Fatalf("expected %v but got %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorAs(t *testing.T) {
|
||||
testErr := errors.New("as")
|
||||
err := &Error{Err: testErr}
|
||||
if expected, got := true, errors.As(err, &testErr); expected != got {
|
||||
t.Fatalf("[testErr as err] expected %v but got %v", expected, got)
|
||||
}
|
||||
if expected, got := true, errors.As(testErr, &err); expected != got {
|
||||
t.Fatalf("[err as testErr] expected %v but got %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupError(t *testing.T) {
|
||||
g := New(0)
|
||||
tests := []string{"error 1", "error 2", "error 3"}
|
||||
for _, tt := range tests {
|
||||
g.Add(errors.New(tt))
|
||||
}
|
||||
|
||||
if expected, got := strings.Join(tests, "\n"), g.Error(); expected != got {
|
||||
t.Fatalf("expected '%s' but got '%s'", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroup(t *testing.T) {
|
||||
const (
|
||||
apiErrorsType = iota + 1
|
||||
childAPIErrorsType
|
||||
childAPIErrors2Type = "string type 1"
|
||||
childAPIErrors2Type1 = "string type 2"
|
||||
|
||||
apiErrorsText = "apiErrors error 1"
|
||||
childAPIErrorsText = "apiErrors:child error 1"
|
||||
childAPIErrors2Text = "apiErrors:child2 error 1"
|
||||
childAPIErrors2Text1 = "apiErrors:child2_1 error 1"
|
||||
)
|
||||
|
||||
g := New(nil)
|
||||
apiErrorsGroup := g.Group(apiErrorsType)
|
||||
apiErrorsGroup.Errf(apiErrorsText)
|
||||
|
||||
childAPIErrorsGroup := apiErrorsGroup.Group(childAPIErrorsType)
|
||||
childAPIErrorsGroup.Addf(childAPIErrorsText)
|
||||
childAPIErrorsGroup2 := apiErrorsGroup.Group(childAPIErrors2Type)
|
||||
childAPIErrorsGroup2.Addf(childAPIErrors2Text)
|
||||
childAPIErrorsGroup2Group1 := childAPIErrorsGroup2.Group(childAPIErrors2Type1)
|
||||
childAPIErrorsGroup2Group1.Addf(childAPIErrors2Text1)
|
||||
|
||||
if apiErrorsGroup.Type != apiErrorsType {
|
||||
t.Fatal("invalid type")
|
||||
}
|
||||
|
||||
if childAPIErrorsGroup.Type != childAPIErrorsType {
|
||||
t.Fatal("invalid type")
|
||||
}
|
||||
|
||||
if childAPIErrorsGroup2.Type != childAPIErrors2Type {
|
||||
t.Fatal("invalid type")
|
||||
}
|
||||
|
||||
if childAPIErrorsGroup2Group1.Type != childAPIErrors2Type1 {
|
||||
t.Fatal("invalid type")
|
||||
}
|
||||
|
||||
if expected, got := 2, len(apiErrorsGroup.children); expected != got {
|
||||
t.Fatalf("expected %d but got %d", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := 0, len(childAPIErrorsGroup.children); expected != got {
|
||||
t.Fatalf("expected %d but got %d", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := 1, len(childAPIErrorsGroup2.children); expected != got {
|
||||
t.Fatalf("expected %d but got %d", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := 0, len(childAPIErrorsGroup2Group1.children); expected != got {
|
||||
t.Fatalf("expected %d but got %d", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := 1, apiErrorsGroup.index; expected != got {
|
||||
t.Fatalf("expected index %d but got %d", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := 2, childAPIErrorsGroup.index; expected != got {
|
||||
t.Fatalf("expected index %d but got %d", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := 3, childAPIErrorsGroup2.index; expected != got {
|
||||
t.Fatalf("expected index %d but got %d", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := 4, childAPIErrorsGroup2Group1.index; expected != got {
|
||||
t.Fatalf("expected index %d but got %d", expected, got)
|
||||
}
|
||||
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
if expected, got :=
|
||||
strings.Join([]string{apiErrorsText, childAPIErrorsText, childAPIErrors2Text, childAPIErrors2Text1}, delim), g.Error(); expected != got {
|
||||
t.Fatalf("expected '%s' but got '%s'", expected, got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Walk", func(t *testing.T) {
|
||||
expectedEntries := 4
|
||||
Walk(g, func(typ interface{}, err error) {
|
||||
g.IncludeChildren = false
|
||||
childAPIErrorsGroup.IncludeChildren = false
|
||||
childAPIErrorsGroup2.IncludeChildren = false
|
||||
childAPIErrorsGroup2Group1.IncludeChildren = false
|
||||
|
||||
expectedEntries--
|
||||
var expected string
|
||||
|
||||
switch typ {
|
||||
case apiErrorsType:
|
||||
expected = apiErrorsText
|
||||
case childAPIErrorsType:
|
||||
expected = childAPIErrorsText
|
||||
case childAPIErrors2Type:
|
||||
expected = childAPIErrors2Text
|
||||
case childAPIErrors2Type1:
|
||||
expected = childAPIErrors2Text1
|
||||
}
|
||||
|
||||
if got := err.Error(); expected != got {
|
||||
t.Fatalf("[%v] expected '%s' but got '%s'", typ, expected, got)
|
||||
}
|
||||
})
|
||||
|
||||
if expectedEntries != 0 {
|
||||
t.Fatalf("not valid number of errors [...%d]", expectedEntries)
|
||||
}
|
||||
|
||||
g.IncludeChildren = true
|
||||
childAPIErrorsGroup.IncludeChildren = true
|
||||
childAPIErrorsGroup2.IncludeChildren = true
|
||||
childAPIErrorsGroup2Group1.IncludeChildren = true
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user