1
0
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:
Gerasimos (Makis) Maropoulos
2019-10-24 18:57:05 +03:00
parent c8236a8d3e
commit 221978e41a
40 changed files with 941 additions and 834 deletions

356
core/errgroup/errgroup.go Normal file
View 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
}

View 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
})
}