// Package errors provides common custom Error types. package errors import ( "fmt" "runtime" "strings" ) // ContextualError is a custom error that can wrap an existing error and provides // additional context based on the function that created the error, if created // via New() type ContextualError struct { Err error Context string Message string } // New creates a ContextualError using the provided message and wrapped Error. // It also attempts to determine additional context automatically based on the // file + function that is calling New(). If the context cannot be determined, // it gracefully degrades into a regular wrapped Error. // // Error context is in the form: // // $package:$file#$line // // edge-infra.dev/pkg/lib/errors.TestContextualError:pkg/errors/contextual_errors_test.go#12 func New(msg string, e error) *ContextualError { err := &ContextualError{Message: msg, Err: e} // attempt to find out information about the caller pc, file, line, ok := runtime.Caller(1) details := runtime.FuncForPC(pc) // if we discovered it, add it as context if ok && details != nil { name := details.Name() // e.g., contextual_error.go:New#20 err.Context = fmt.Sprintf("%s:%s#%d", name, file, line) } return err } // Error builds the error string based on the ContextualError state func (c *ContextualError) Error() string { if c.Message == "" { c.Message = "failed." } str := c.Message // add punctuation if not present if !(strings.HasSuffix(c.Message, ".") || strings.HasSuffix(c.Message, "!") || strings.HasSuffix(c.Message, "?")) { str = fmt.Sprintf("%s.", str) } // add wrapped error text if present if c.Err != nil { str = fmt.Sprintf("%s error: %v", str, c.Err) } // add caller context if present if c.Context != "" { str = fmt.Sprintf("%s: %s", c.Context, str) } return str } func (c *ContextualError) Unwrap() error { return c.Err } // Wrap creates a ContextualError without a message func Wrap(e error) *ContextualError { return New("", e) }