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