github.com/gocaveman/caveman@v0.0.0-20191211162744-0ddf99dbdf6e/weberrors/weberrors.go (about)

     1  /*
     2  Package weberrors augments errors with an additional code, data and message for convenience while writing web applications.
     3  It also provides wrapping and unwrapping where a Cause() method undicates an underlying error.
     4  
     5  The New() method will wrap the error your provide and return a new one with the provided code, message and data.
     6  These fields correspond to the JSON RPC 2.0 error code, message and data fields.
     7  They are also very useful when returning errors from REST calls.
     8  
     9  The Causer interface specifies the method Cause() error which can be used to
    10  retrieve an underlying error - it's cause.  Any time an error is wrapped by a method in this package
    11  the returned error will implement Causer.
    12  
    13  The methods ErrorCode(error) int, ErrorMessage(error) string and ErrorData(error) interface{}
    14  provide access to each of the fields wrapped with a New() call, and will "unwrap" the error by calling Cause() as necessary
    15  to extract the needed value.  The httpapi uses these methods to extract information when
    16  
    17  Note that weberrors.ErrorMessage(err) and err.Error() will generally return two completely different things - the former
    18  being the public-facing error message returned to the caller and the latter being the internal error message which is usually
    19  either checked for and handled or logged.
    20  
    21  	// start with this error
    22  	var baseErr = fmt.Errorf("base error")
    23  
    24  	// wrap it with web info
    25  	var webErr = weberrors.New(baseErr,
    26  		501,
    27  		"Something went wrong",
    28  		"additional data")
    29  
    30  	// also specify the location
    31  	var finalErr = weberrors.ErrLoc(webErr)
    32  
    33  	// let's see what info we can extract from it
    34  	fmt.Printf("Error: %v\n", finalErr)
    35  	fmt.Printf("Error Cause: %v\n", weberrors.RootCause(finalErr))
    36  	fmt.Printf("Error Code: %v\n", weberrors.ErrorCode(finalErr))
    37  	fmt.Printf("Error Message: %v\n", weberrors.ErrorMessage(finalErr))
    38  	fmt.Printf("Error Data: %v\n", weberrors.ErrorData(finalErr))
    39  
    40  */
    41  package weberrors
    42  
    43  import (
    44  	"fmt"
    45  	"math/rand"
    46  	"net/http"
    47  	"runtime"
    48  	"time"
    49  )
    50  
    51  // NewCode returns an error with the cause and code you provide.
    52  func NewCode(err error, code int) error {
    53  	if err == nil {
    54  		panic("nil cause error passed to weberrors.NewCode")
    55  	}
    56  	return &errCode{
    57  		Err:     err,
    58  		Code:    code,
    59  		Headers: setErrID(nil),
    60  	}
    61  }
    62  
    63  type errCode struct {
    64  	Err     error       `json:"-"`
    65  	Code    int         `json:"code,omitempty"`
    66  	Headers http.Header `json:"-"`
    67  }
    68  
    69  func (c *errCode) Cause() error {
    70  	return c.Err
    71  }
    72  
    73  func (c *errCode) Error() string {
    74  	if c.Err != nil {
    75  		return c.Err.Error()
    76  	}
    77  	return ""
    78  }
    79  
    80  func (c *errCode) ErrorHeaders() http.Header {
    81  	return c.Headers
    82  }
    83  
    84  // New returns a error instance with the cause error, code, message, data and headers you provide.
    85  func New(err error, code int, message string, data interface{}, headers http.Header) error {
    86  	if err == nil {
    87  		panic("nil cause error passed to weberrors.New")
    88  	}
    89  	return &detail{
    90  		Err:     err,
    91  		Code:    code,
    92  		Message: message,
    93  		Data:    data,
    94  		Headers: setErrID(headers),
    95  	}
    96  }
    97  
    98  type detail struct {
    99  	Err     error       `json:"-"`
   100  	Code    int         `json:"code,omitempty"`
   101  	Message string      `json:"message,omitempty"`
   102  	Data    interface{} `json:"data,omitempty"`
   103  	Headers http.Header `json:"-"`
   104  }
   105  
   106  // Cause returns the underlying error (field Err).
   107  // We use the name Cause from github.com/pkg/errors, since
   108  // the Go2 Error Value Draft Proposal, which suggests the name Unwrap, is not standardized yet
   109  // (see https://go.googlesource.com/proposal/+/master/design/go2draft-error-values-overview.md).
   110  func (d *detail) Cause() error {
   111  	return d.Err
   112  }
   113  
   114  // Error implements the error interface by returning the Err.Error().
   115  // This corresponds to the internal message.
   116  func (d *detail) Error() string {
   117  	if d.Err != nil {
   118  		return d.Err.Error()
   119  	}
   120  	return d.Message
   121  }
   122  
   123  // ErrorCode returns the error code.
   124  func (d *detail) ErrorCode() int {
   125  	return d.Code
   126  }
   127  
   128  // ErrorMessage returns the public error message.
   129  func (d *detail) ErrorMessage() string {
   130  	return d.Message
   131  }
   132  
   133  // ErrorData returns the data.
   134  func (d *detail) ErrorData() interface{} {
   135  	return d.Data
   136  }
   137  
   138  // ErrorHeaders returns additional headers to be set on an HTTP response
   139  func (d *detail) ErrorHeaders() http.Header {
   140  	return d.Headers
   141  }
   142  
   143  // ErrorCoder has an ErrorCode() method.
   144  type ErrorCoder interface {
   145  	ErrorCode() int
   146  }
   147  
   148  // ErrorCode unwraps the error provided and returns the result of calling ErrorCode() on the first error that has implements it.
   149  // Errors are unwrapped by calling Cause().
   150  func ErrorCode(err error) int {
   151  
   152  	for err != nil {
   153  		ec, ok := err.(ErrorCoder)
   154  		if ok {
   155  			return ec.ErrorCode()
   156  		}
   157  		c, ok := err.(Causer)
   158  		if !ok {
   159  			break
   160  		}
   161  		err = c.Cause()
   162  	}
   163  
   164  	return 0
   165  }
   166  
   167  // ErrorMessager has an ErrorMessage() method.
   168  type ErrorMessager interface {
   169  	ErrorMessage() string
   170  }
   171  
   172  // ErrorMessage unwraps the error provided and returns the result of calling ErrorMessage() on the first error that has implements it.
   173  // Errors are unwrapped by calling Cause().
   174  func ErrorMessage(err error) string {
   175  
   176  	for err != nil {
   177  		ec, ok := err.(ErrorMessager)
   178  		if ok {
   179  			return ec.ErrorMessage()
   180  		}
   181  		c, ok := err.(Causer)
   182  		if !ok {
   183  			break
   184  		}
   185  		err = c.Cause()
   186  	}
   187  
   188  	return ""
   189  }
   190  
   191  // ErrorDataer has an ErrorData() method.
   192  type ErrorDataer interface {
   193  	ErrorData() interface{}
   194  }
   195  
   196  // ErrorData unwraps the error provided and returns the result of calling ErrorData() on the first error that has implements it.
   197  // Errors are unwrapped by calling Cause().
   198  func ErrorData(err error) interface{} {
   199  
   200  	for err != nil {
   201  		ec, ok := err.(ErrorDataer)
   202  		if ok {
   203  			return ec.ErrorData()
   204  		}
   205  		c, ok := err.(Causer)
   206  		if !ok {
   207  			break
   208  		}
   209  		err = c.Cause()
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  // ErrorHeaderser is the interface for ErrorHeaders.
   216  // The name is so bad it's good.  A bit like Plan 9 from Outer Space.
   217  type ErrorHeaderser interface {
   218  	ErrorHeaders() http.Header
   219  }
   220  
   221  // ErrorHeaders unwraps the error provided and returns a composite of all of the headers found during the
   222  // unwrapping, with higher level values overwritting lower level ones.
   223  func ErrorHeaders(err error) http.Header {
   224  
   225  	var ret http.Header
   226  	addRet := func(h http.Header) {
   227  		if len(h) == 0 {
   228  			return
   229  		}
   230  		if ret == nil {
   231  			ret = make(http.Header)
   232  		}
   233  		// write each key that doesn't exist in the return
   234  		for k, v := range h {
   235  			if _, ok := ret[k]; !ok {
   236  				ret[k] = v
   237  			}
   238  		}
   239  	}
   240  
   241  	for err != nil {
   242  		eh, ok := err.(ErrorHeaderser)
   243  		if ok {
   244  			addRet(eh.ErrorHeaders()) // use whatever headers are there
   245  		}
   246  		c, ok := err.(Causer)
   247  		if !ok {
   248  			break
   249  		}
   250  		err = c.Cause()
   251  	}
   252  
   253  	return ret
   254  }
   255  
   256  // RootCause calls Cause() recursively until it finds an error that doesn't implement it and returns that.
   257  // If Cause() returns nil then that will be returned.
   258  func RootCause(err error) error {
   259  	for err != nil {
   260  		c, ok := err.(Causer)
   261  		if !ok {
   262  			break
   263  		}
   264  		err = c.Cause()
   265  		if err == c.(error) { // avoid infinite loop
   266  			break
   267  		}
   268  	}
   269  	return err
   270  }
   271  
   272  // Causer allows you to get the underlying error that caused this one, if available.
   273  // (It incidentally is compatibile with github.com/pkg/errors).  It is recommended
   274  // that only errors which have a cause implement this interface.
   275  type Causer interface {
   276  	Cause() error
   277  }
   278  
   279  type errPrefix struct {
   280  	cause  error
   281  	prefix string
   282  }
   283  
   284  // Cause returns the underlying error.
   285  func (ep *errPrefix) Cause() error {
   286  	return ep.cause
   287  }
   288  
   289  // Error returns the cause error prefixed by the strinct provided
   290  func (ep *errPrefix) Error() string {
   291  	return ep.prefix + ep.cause.Error()
   292  }
   293  
   294  // ErrPrefix returns an error whose Error() method return will have the specified prefix.
   295  // The error provided must not be nil and is set as the cause for the error returned.
   296  func ErrPrefix(prefix string, err error) error {
   297  	return &errPrefix{
   298  		prefix: prefix,
   299  		cause:  err,
   300  	}
   301  }
   302  
   303  type errLoc struct {
   304  	cause    error
   305  	location string
   306  }
   307  
   308  // Cause returns the underlying error.
   309  func (el *errLoc) Cause() error {
   310  	return el.cause
   311  }
   312  
   313  // Error returns the cause error prefixed by location information (file and line number).
   314  func (el *errLoc) Error() string {
   315  	return el.location + ": " + el.cause.Error()
   316  }
   317  
   318  // ErrLoc wraps an error so it's Error() method will return the same text prefixed
   319  // with the file and line number it was called from.  A nil error value will return nil.
   320  // The returned value also has a Cause() method which will return the underlying error.
   321  func ErrLoc(err error) error {
   322  
   323  	if err == nil {
   324  		return nil
   325  	}
   326  
   327  	_, file, line, _ := runtime.Caller(1)
   328  	return &errLoc{
   329  		cause:    err,
   330  		location: fmt.Sprintf("%s:%v", file, line),
   331  	}
   332  }
   333  
   334  var rnd = rand.New(rand.NewSource(time.Now().Unix()))
   335  
   336  func errID() string {
   337  	return fmt.Sprintf("%020d", rnd.Uint64())
   338  }
   339  
   340  func setErrID(h http.Header) http.Header {
   341  	if h == nil {
   342  		h = make(http.Header)
   343  	}
   344  	if h.Get("X-Id") == "" {
   345  		h.Set("X-Id", errID())
   346  	}
   347  	return h
   348  }