Description
Description
It is common practice in golang to return an error from a func in the event of a problem during its execution (e.g. func myFunc() error { ... }
).
However, there are a few cases, such as field validation on a struct, where it is useful to return multiple errors from a function (e.g. func validate(m *Model) []error
). Sometimes these errors need to be 'bubbled up' through a call stack that only returns one error. So, I find myself having to re-implement custom errors that represent many errors in each of my projects.
I think a generic solution could be added to the standard libraries' errors
package that serves this purpose.
Example Implementation
I've updated the example implementation to incorporate the feedback from @neild and @D1CED
package util
import (
"fmt"
)
// Errors are many errors that can be represented as a single error
type Errors interface {
Errors() []error
error
}
// NewErrors combine many errors into a single error
func NewErrors(errs ...error) error {
if lenErrs := len(errs); lenErrs == 0 {
return nil
} else if lenErrs == 1 {
return errs[0]
}
var es manyErrors
for _, err := range errs {
// Merge many slices of errors into a single slice
if errs, ok := err.(Errors); ok {
es = append(es, errs.Errors()...)
continue
}
es = append(es, err)
}
return es
}
// manyErrors combines many errors into a single error
type manyErrors []error
// Is works with errors.Is
func (es errors) Is(find error) bool {
for _, e := range es {
if Is(e, find) {
return true
}
}
return false
}
// As works with errors.As
func (es errors) As(find interface{}) bool {
for _, e := range es {
if As(e, find) {
return true
}
}
return false
}
// Errors implements the Errors interface
func (es manyErrors) Errors() []error {
return []error(es)
}
// Error implements the error interface
func (es manyErrors) Error() string {
return fmt.Sprintf("[%v", []error(es))
}
Example Usage
import "errors"
type Model struct {
FieldOne string `json:"field_one"`
FieldTwo string `json:"field_two"`
FieldThree string `json:"field_three"`
}
func (m *Model) Validate() error {
var errs []error
if len(m.FieldOne) < 1 {
errs = append(errs, errors.New("'field_one' is required"))
}
if len(m.FieldTwo) < 1 {
errs = append(errs, errors.New("'field_two' is required"))
}
if len(m.FieldThree) < 1 {
errs = append(errs, errors.New("'field_three' is required"))
}
return errors.NewErrors(errs...)
}
Conclusion
Whether or not this proposal is accepted, I'm curious how others have approached this problem in the past and if there's already a commonly referenced solution that I'm unfamiliar with. Thanks 🙏