blob: cd3269ce81787b0590e117667710afdba3789cf5 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 errors contains functionality for creating and wrapping errors with improved formatting
// compared to the standard Go error functionality.
package errors
import (
"fmt"
"io"
"strings"
)
// New returns an error with the given message.
func New(message string) error {
return fmt.Errorf("%s", message)
}
// Errorf returns an error with a message formatted according to the format
// specifier.
func Errorf(format string, args ...interface{}) error {
return fmt.Errorf(format, args...)
}
// Wrap returns a new error annotating err with a new message.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
return &beamError{
cause: err,
msg: message,
top: getTop(err),
}
}
// Wrapf returns a new error annotating err with a new message according to
// the format specifier.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &beamError{
cause: err,
msg: fmt.Sprintf(format, args...),
top: getTop(err),
}
}
// WithContext returns a new error adding additional context to err.
func WithContext(err error, context string) error {
if err == nil {
return nil
}
return &beamError{
cause: err,
context: context,
top: getTop(err),
}
}
// WithContextf returns a new error adding additional context to err according
// to the format specifier.
func WithContextf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &beamError{
cause: err,
context: fmt.Sprintf(format, args...),
top: getTop(err),
}
}
// SetTopLevelMsg returns a new error with the given top level message. The top
// level message is the first error message that gets printed when Error()
// is called on the returned error or any error wrapping it.
func SetTopLevelMsg(err error, top string) error {
if err == nil {
return nil
}
return &beamError{
cause: err,
top: top,
}
}
// SetTopLevelMsgf returns a new error with the given top level message
// according to the format specifier. The top level message is the first error
// message that gets printed when Error() is called on the returned error or
// any error wrapping it.
func SetTopLevelMsgf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &beamError{
cause: err,
top: fmt.Sprintf(format, args...),
}
}
func getTop(e error) string {
if be, ok := e.(*beamError); ok {
return be.top
}
return e.Error()
}
// beamError represents one or more details about an error. They are usually
// nested in the order that additional context was wrapped around the original
// error.
//
// The presence or lack of certain fields implicitly indicates some details
// about the error.
//
// * If no cause is present it indicates that this instance is the original
// error, and the message is assumed to be present.
// * If both message and context are present, the context describes this error
// not the next error.
// * top is always assumed to be present since it is propogated up from the
// original error if not explicitly set.
type beamError struct {
cause error // The error being wrapped. If nil then this is the first error.
context string // Adds additional context to this error and any following.
msg string // Message describing an error.
top string // The first error message to display to a user. Propogated upwards.
}
// Error outputs a beamError as a string. The top-level error message is
// displayed first, followed by each error's context and error message in
// sequence. The original error is output last.
func (e *beamError) Error() string {
var builder strings.Builder
if e.top != "" {
builder.WriteString(fmt.Sprintf("%s\nFull error:\n", e.top))
}
e.printRecursive(&builder)
return builder.String()
}
// printRecursive outputs the contexts and messages of beamErrors recursively
// while ignoring the top-level error. This avoids calling Error recursively on
// beamErrors since that would repeatedly print top-level messages.
func (e *beamError) printRecursive(builder *strings.Builder) {
wraps := e.cause != nil
if e.context != "" {
builder.WriteString(fmt.Sprintf("\t%s:\n", e.context))
}
if e.msg != "" {
builder.WriteString(e.msg)
if wraps {
builder.WriteString("\nCaused by:\n")
}
}
if wraps {
if be, ok := e.cause.(*beamError); ok {
be.printRecursive(builder)
} else {
builder.WriteString(e.cause.Error())
}
}
}
func (e *beamError) Format(s fmt.State, verb rune) {
switch verb {
case 'v', 's':
io.WriteString(s, e.Error())
case 'q':
fmt.Fprintf(s, "%q", e.Error())
}
}