mirror of
https://github.com/gomods/athens
synced 2026-02-03 15:30:32 +00:00
201 lines
4.6 KiB
Go
201 lines
4.6 KiB
Go
package errors
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"runtime"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Kind enums.
|
|
const (
|
|
KindNotFound = http.StatusNotFound
|
|
KindBadRequest = http.StatusBadRequest
|
|
KindUnexpected = http.StatusInternalServerError
|
|
KindAlreadyExists = http.StatusConflict
|
|
KindRateLimit = http.StatusTooManyRequests
|
|
KindNotImplemented = http.StatusNotImplemented
|
|
KindRedirect = http.StatusMovedPermanently
|
|
KindGatewayTimeout = http.StatusGatewayTimeout
|
|
)
|
|
|
|
// Error is an Athens system error.
|
|
// It carries information and behavior
|
|
// as to what caused this error so that
|
|
// callers can implement logic around it.
|
|
type Error struct {
|
|
// Kind categories Athens errors into a smaller
|
|
// subset of errors. This way we can generalize
|
|
// what an error really is: such as "not found",
|
|
// "bad request", etc. The official categories
|
|
// are HTTP status code but the ones we use are
|
|
// imported into this package.
|
|
Kind int
|
|
Op Op
|
|
Module M
|
|
Version V
|
|
Err error
|
|
Severity logrus.Level
|
|
}
|
|
|
|
// Error returns the underlying error's
|
|
// string message. The logger takes care
|
|
// of filling out the stack levels and
|
|
// extra information.
|
|
func (e Error) Error() string {
|
|
return e.Err.Error()
|
|
}
|
|
|
|
func (e Error) Unwrap() error { return e.Err }
|
|
|
|
// Is is a shorthand for checking an error against a kind.
|
|
func Is(err error, kind int) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
return Kind(err) == kind
|
|
}
|
|
|
|
// IsErr is a convenience wrapper around the std library errors.Is.
|
|
func IsErr(err, target error) bool {
|
|
return errors.Is(err, target)
|
|
}
|
|
|
|
// AsErr is a convenience wrapper around the std library errors.As.
|
|
func AsErr(err error, target any) bool {
|
|
return errors.As(err, target)
|
|
}
|
|
|
|
// Op describes any independent function or
|
|
// method in Athens. A series of operations
|
|
// forms a more readable stack trace.
|
|
type Op string
|
|
|
|
func (o Op) String() string {
|
|
return string(o)
|
|
}
|
|
|
|
// M represents a module in an error
|
|
// this is so that we can distinguish
|
|
// a module from a regular error string or version.
|
|
type M string
|
|
|
|
// V represents a module version in an error.
|
|
type V string
|
|
|
|
// E is a helper function to construct an Error type
|
|
// Operation always comes first, module path and version
|
|
// come second, they are optional. Args must have at least
|
|
// an error or a string to describe what exactly went wrong.
|
|
// You can optionally pass a Logrus severity to indicate
|
|
// the log level of an error based on the context it was constructed in.
|
|
func E(op Op, args ...any) error {
|
|
e := Error{Op: op}
|
|
if len(args) == 0 {
|
|
msg := "errors.E called with 0 args"
|
|
_, file, line, ok := runtime.Caller(1)
|
|
if ok {
|
|
msg = fmt.Sprintf("%v - %v:%v", msg, file, line)
|
|
}
|
|
e.Err = errors.New(msg)
|
|
}
|
|
for _, a := range args {
|
|
switch a := a.(type) {
|
|
case error:
|
|
e.Err = a
|
|
case string:
|
|
e.Err = errors.New(a)
|
|
case M:
|
|
e.Module = a
|
|
case V:
|
|
e.Version = a
|
|
case logrus.Level:
|
|
e.Severity = a
|
|
case int:
|
|
e.Kind = a
|
|
}
|
|
}
|
|
if e.Err == nil {
|
|
e.Err = errors.New(KindText(e))
|
|
}
|
|
return e
|
|
}
|
|
|
|
// Severity returns the log level of an error
|
|
// if none exists, then the level is Error because
|
|
// it is an unexpected.
|
|
func Severity(err error) logrus.Level {
|
|
var e Error
|
|
if !errors.As(err, &e) {
|
|
return logrus.ErrorLevel
|
|
}
|
|
|
|
// if there's no severity (0 is Panic level in logrus
|
|
// which we should not use since cloud providers only have
|
|
// debug, info, warn, and error) then look for the
|
|
// child's severity.
|
|
if e.Severity < logrus.ErrorLevel {
|
|
return Severity(e.Err)
|
|
}
|
|
|
|
return e.Severity
|
|
}
|
|
|
|
// Expect is a helper that returns an Info level
|
|
// if the error has the expected kind, otherwise
|
|
// it returns an Error level.
|
|
func Expect(err error, kinds ...int) logrus.Level {
|
|
for _, kind := range kinds {
|
|
if Kind(err) == kind {
|
|
return logrus.InfoLevel
|
|
}
|
|
}
|
|
return logrus.ErrorLevel
|
|
}
|
|
|
|
// Kind recursively searches for the
|
|
// first error kind it finds.
|
|
func Kind(err error) int {
|
|
var e Error
|
|
if !errors.As(err, &e) {
|
|
return KindUnexpected
|
|
}
|
|
|
|
if e.Kind != 0 {
|
|
return e.Kind
|
|
}
|
|
|
|
return Kind(e.Err)
|
|
}
|
|
|
|
// KindText returns a friendly string
|
|
// of the Kind type. Since we use http
|
|
// status codes to represent error kinds,
|
|
// this method just deferrs to the net/http
|
|
// text representations of statuses.
|
|
func KindText(err error) string {
|
|
return http.StatusText(Kind(err))
|
|
}
|
|
|
|
// Ops aggregates the error's operation
|
|
// with all the embedded errors' operations.
|
|
// This way you can construct a queryable
|
|
// stack trace.
|
|
func Ops(err Error) []Op {
|
|
ops := []Op{err.Op}
|
|
for {
|
|
//nolint:errorlint // We iterate the errors anyway.
|
|
embeddedErr, ok := err.Err.(Error)
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
ops = append(ops, embeddedErr.Op)
|
|
err = embeddedErr
|
|
}
|
|
|
|
return ops
|
|
}
|