error.go (143 lines of code) (raw):
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package daisy
import (
"fmt"
"strings"
)
const (
untypedError = ""
multiError = "MultiError"
fileIOError = "FileIOError"
resourceDNEError = "ResourceDoesNotExist"
imageObsoleteDeletedError = "ImageObsoleteOrDeleted"
invalidInputError = "InvalidInputError"
apiError = "APIError"
apiError404 = "APIError404"
)
// DError is a Daisy external error type.
// It has:
// - optional error typing
// - multiple error aggregation
// - safe error messages in which privacy information is removed
//
// Default implementation:
// The default DError implementation is flat, DError.add(anotherDErr) will merge the two dErrs
// into a single, flat DError instead of making anotherDErr a child to DError.
type DError interface {
error
// add shouldn't be called directly, instead call addErrs(DError, error).
// This assists with nil dErrs. addErrs(nil, e) will return a new DError.
add(error)
etype() string
errors() []error
errorsType() []string
AnonymizedErrs() []string
CausedByErrType(t string) bool
}
// addErrs adds an error to a DError.
// The DError can be nil. If both the DError and errors are nil, a nil DError is returned.
// If DError is nil, but errors are not nil, a new DError is instantiated, the errors are added,
// and the new DError is returned.
// Any nil error in errs is disregarded. Therefore, `var e DError; e = addErrs(e, nil)`
// preserves e's nil-ness.
func addErrs(e DError, errs ...error) DError {
for _, err := range errs {
if err != nil {
if e == nil {
e = &dErrImpl{}
}
e.add(err)
}
}
return e
}
// Errf returns a DError by constructing error message with given format.
func Errf(format string, a ...interface{}) DError {
return newErr(format, fmt.Errorf(format, a...))
}
// wrapErrf returns a DError by keeping errors type and replacing original error message.
func wrapErrf(e DError, formatPrefix string, a ...interface{}) DError {
f := fmt.Sprintf("%v: %v", formatPrefix, strings.Join(e.AnonymizedErrs(), "; "))
return &dErrImpl{
errs: []error{fmt.Errorf("%v: %v", fmt.Sprintf(formatPrefix, a...), e.Error())},
errsType: e.errorsType(),
anonymizedErrs: []string{f},
}
}
// newErr returns a DError. newErr is used to wrap another error as a DError.
// If e is already a DError, e is copied and returned.
// If e is a normal error, anonymizedErrMsg is used to hide privacy info.
// If e is nil, nil is returned.
func newErr(anonymizedErrMsg string, e error) DError {
if e == nil {
return nil
}
if dE, ok := e.(*dErrImpl); ok {
return dE
}
return &dErrImpl{errs: []error{e}, errsType: []string{""}, anonymizedErrs: []string{anonymizedErrMsg}}
}
// ToDError returns a DError. ToDError is used to wrap another error as a DError.
// If e is already a DError, e is copied and returned.
// If e is a normal error, error message is reused as format.
// If e is nil, nil is returned.
func ToDError(e error) DError {
if e == nil {
return nil
}
if dE, ok := e.(*dErrImpl); ok {
return dE
}
return &dErrImpl{errs: []error{e}, errsType: []string{""}, anonymizedErrs: []string{e.Error()}}
}
func typedErr(errType string, safeErrMsg string, e error) DError {
if e == nil {
return nil
}
safeErrMsg = fmt.Sprintf("%v: %v", errType, safeErrMsg)
dE := newErr(safeErrMsg, e)
dE.(*dErrImpl).errsType = []string{errType}
return dE
}
func typedErrf(errType, format string, a ...interface{}) DError {
return typedErr(errType, format, fmt.Errorf(format, a...))
}
type dErrImpl struct {
errs []error
errsType []string
anonymizedErrs []string
}
func (e *dErrImpl) add(err error) {
if e2, ok := err.(*dErrImpl); ok {
e.merge(e2)
} else if !ok {
// This is some other error type. Add it.
e.errs = append(e.errs, err)
e.errsType = append(e.errsType, "")
}
}
func (e *dErrImpl) Error() string {
if e.len() == 0 {
return ""
}
if e.len() == 1 {
errStr := e.errs[0].Error()
if len(e.errsType) == 1 && e.errsType[0] != "" {
return fmt.Sprintf("%s: %s", e.errsType[0], errStr)
}
return errStr
}
// Multiple error handling.
pre := "* "
lines := make([]string, e.len())
for i, err := range e.errs {
lines[i] = pre + err.Error()
}
return "Multiple errors:\n" + strings.Join(lines, "\n")
}
func (e *dErrImpl) AnonymizedErrs() []string {
return e.anonymizedErrs
}
func (e *dErrImpl) len() int {
return len(e.errs)
}
func (e *dErrImpl) merge(e2 *dErrImpl) {
if e2.len() > 0 {
e.errs = append(e.errs, e2.errs...)
e.errsType = append(e.errsType, e2.errsType...)
e.anonymizedErrs = append(e.anonymizedErrs, e2.anonymizedErrs...)
}
}
func (e *dErrImpl) etype() string {
if e.len() > 1 {
return multiError
} else if e.len() == 1 && len(e.errsType) == 1 {
return e.errsType[0]
} else {
return ""
}
}
func (e *dErrImpl) errorsType() []string {
return e.errsType
}
func (e *dErrImpl) errors() []error {
return e.errs
}
func (e *dErrImpl) CausedByErrType(t string) bool {
for _, et := range e.errsType {
if et == t {
return true
}
}
return false
}