// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package errors implements functions to manipulate errors. package errors import ( "fmt" "sort" "strings" ) // TODO: This package currently only provides functionality for constructing // non-fatal errors. However, it does not currently provide functionality // to test for a specific kind of non-fatal error, which is necessary // for the end user. // // When that functionality is added, we need to think carefully about whether // a user only cares that some kind of non-fatal error was present or whether // all of the errors are of the same kind of non-fatal error. // NonFatalErrors is a list of non-fatal errors where each error // must either be a RequiredNotSet error or InvalidUTF8 error. // The list must not be empty. type NonFatalErrors []error func (es NonFatalErrors) Error() string { ms := map[string]struct{}{} for _, e := range es { ms[e.Error()] = struct{}{} } var ss []string for s := range ms { ss = append(ss, s) } sort.Strings(ss) return "proto: " + strings.Join(ss, "; ") } // NonFatal contains non-fatal errors, which are errors that permit execution // to continue, but should return with a non-nil error. As such, NonFatal is // a data structure useful for swallowing non-fatal errors, but being able to // reproduce them at the end of the function. // An error is non-fatal if it is collection of non-fatal errors, or is // an individual error where IsRequiredNotSet or IsInvalidUTF8 reports true. // // Typical usage pattern: // var nerr errors.NonFatal // ... // if err := MyFunction(); !nerr.Merge(err) { // return nil, err // immediately return if err is fatal // } // ... // return out, nerr.E type NonFatal struct{ E error } // Merge merges err into nf and reports whether it was successful. // Otherwise it returns false for any fatal non-nil errors. func (nf *NonFatal) Merge(err error) (ok bool) { if err == nil { return true // not an error } if es, ok := err.(NonFatalErrors); ok { nf.append(es...) return true // merged a list of non-fatal errors } if e, ok := err.(interface{ RequiredNotSet() bool }); ok && e.RequiredNotSet() { nf.append(err) return true // non-fatal RequiredNotSet error } if e, ok := err.(interface{ InvalidUTF8() bool }); ok && e.InvalidUTF8() { nf.append(err) return true // non-fatal InvalidUTF8 error } return false // fatal error } // AppendRequiredNotSet appends a RequiredNotSet error. func (nf *NonFatal) AppendRequiredNotSet(field string) { nf.append(requiredNotSetError(field)) } // AppendInvalidUTF8 appends an InvalidUTF8 error. func (nf *NonFatal) AppendInvalidUTF8(field string) { nf.append(invalidUTF8Error(field)) } func (nf *NonFatal) append(errs ...error) { es, _ := nf.E.(NonFatalErrors) es = append(es, errs...) nf.E = es } type requiredNotSetError string func (e requiredNotSetError) Error() string { if e == "" { return "required field not set" } return string("required field " + e + " not set") } func (requiredNotSetError) RequiredNotSet() bool { return true } type invalidUTF8Error string func (e invalidUTF8Error) Error() string { if e == "" { return "invalid UTF-8 detected" } return string("field " + e + " contains invalid UTF-8") } func (invalidUTF8Error) InvalidUTF8() bool { return true } // New formats a string according to the format specifier and arguments and // returns an error that has a "proto" prefix. func New(f string, x ...interface{}) error { for i := 0; i < len(x); i++ { if e, ok := x[i].(prefixError); ok { x[i] = e.s // avoid "proto: " prefix when chaining } } return &prefixError{s: fmt.Sprintf(f, x...)} } type prefixError struct{ s string } func (e *prefixError) Error() string { return "proto: " + e.s }