blob: dde5d811c801f06513605dd36ea942a3fbf9b7c0 [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 ""
}
// 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 cause of this error.
// * top is always propogated up from the cause. If it's empty that means that
// it was never set on any error in the sequence.
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 is a helper function for outputting the contexts and messages
// of a sequence of beamErrors.
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("\n\tcaused 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())
}
}