mirror of
https://github.com/hibiken/asynq.git
synced 2024-11-10 11:31:58 +08:00
289 lines
8.2 KiB
Go
289 lines
8.2 KiB
Go
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
// Use of this source code is governed by a MIT license
|
|
// that can be found in the LICENSE file.
|
|
|
|
// Package errors defines the error type and functions used by
|
|
// asynq and its internal packages.
|
|
package errors
|
|
|
|
// Note: This package is inspired by a blog post about error handling in project Upspin
|
|
// https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html.
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
// Error is the type that implements the error interface.
|
|
// It contains a number of fields, each of different type.
|
|
// An Error value may leave some values unset.
|
|
type Error struct {
|
|
Code Code
|
|
Op Op
|
|
Err error
|
|
}
|
|
|
|
func (e *Error) DebugString() string {
|
|
var b strings.Builder
|
|
if e.Op != "" {
|
|
b.WriteString(string(e.Op))
|
|
}
|
|
if e.Code != Unspecified {
|
|
if b.Len() > 0 {
|
|
b.WriteString(": ")
|
|
}
|
|
b.WriteString(e.Code.String())
|
|
}
|
|
if e.Err != nil {
|
|
if b.Len() > 0 {
|
|
b.WriteString(": ")
|
|
}
|
|
b.WriteString(e.Err.Error())
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func (e *Error) Error() string {
|
|
var b strings.Builder
|
|
if e.Code != Unspecified {
|
|
b.WriteString(e.Code.String())
|
|
}
|
|
if e.Err != nil {
|
|
if b.Len() > 0 {
|
|
b.WriteString(": ")
|
|
}
|
|
b.WriteString(e.Err.Error())
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func (e *Error) Unwrap() error {
|
|
return e.Err
|
|
}
|
|
|
|
// Code defines the canonical error code.
|
|
type Code uint8
|
|
|
|
// List of canonical error codes.
|
|
const (
|
|
Unspecified Code = iota
|
|
NotFound
|
|
FailedPrecondition
|
|
Internal
|
|
AlreadyExists
|
|
Unknown
|
|
// Note: If you add a new value here, make sure to update String method.
|
|
)
|
|
|
|
func (c Code) String() string {
|
|
switch c {
|
|
case Unspecified:
|
|
return "ERROR_CODE_UNSPECIFIED"
|
|
case NotFound:
|
|
return "NOT_FOUND"
|
|
case FailedPrecondition:
|
|
return "FAILED_PRECONDITION"
|
|
case Internal:
|
|
return "INTERNAL_ERROR"
|
|
case AlreadyExists:
|
|
return "ALREADY_EXISTS"
|
|
case Unknown:
|
|
return "UNKNOWN"
|
|
}
|
|
panic(fmt.Sprintf("unknown error code %d", c))
|
|
}
|
|
|
|
// Op describes an operation, usually as the package and method,
|
|
// such as "rdb.Enqueue".
|
|
type Op string
|
|
|
|
// E builds an error value from its arguments.
|
|
// There must be at least one argument or E panics.
|
|
// The type of each argument determines its meaning.
|
|
// If more than one argument of a given type is presented,
|
|
// only the last one is recorded.
|
|
//
|
|
// The types are:
|
|
// errors.Op
|
|
// The operation being performed, usually the method
|
|
// being invoked (Get, Put, etc.).
|
|
// errors.Code
|
|
// The canonical error code, such as NOT_FOUND.
|
|
// string
|
|
// Treated as an error message and assigned to the
|
|
// Err field after a call to errors.New.
|
|
// error
|
|
// The underlying error that triggered this one.
|
|
//
|
|
// If the error is printed, only those items that have been
|
|
// set to non-zero values will appear in the result.
|
|
func E(args ...interface{}) error {
|
|
if len(args) == 0 {
|
|
panic("call to errors.E with no arguments")
|
|
}
|
|
e := &Error{}
|
|
for _, arg := range args {
|
|
switch arg := arg.(type) {
|
|
case Op:
|
|
e.Op = arg
|
|
case Code:
|
|
e.Code = arg
|
|
case error:
|
|
e.Err = arg
|
|
case string:
|
|
e.Err = errors.New(arg)
|
|
default:
|
|
_, file, line, _ := runtime.Caller(1)
|
|
log.Printf("errors.E: bad call from %s:%d: %v", file, line, args)
|
|
return fmt.Errorf("unknown type %T, value %v in error call", arg, arg)
|
|
}
|
|
}
|
|
return e
|
|
}
|
|
|
|
// CanonicalCode returns the canonical code of the given error if one is present.
|
|
// Otherwise it returns Unspecified.
|
|
func CanonicalCode(err error) Code {
|
|
if err == nil {
|
|
return Unspecified
|
|
}
|
|
e, ok := err.(*Error)
|
|
if !ok {
|
|
return Unspecified
|
|
}
|
|
if e.Code == Unspecified {
|
|
return CanonicalCode(e.Err)
|
|
}
|
|
return e.Code
|
|
}
|
|
|
|
/******************************************
|
|
Domin Specific Error Types & Values
|
|
*******************************************/
|
|
|
|
var (
|
|
// ErrNoProcessableTask indicates that there are no tasks ready to be processed.
|
|
ErrNoProcessableTask = errors.New("no tasks are ready for processing")
|
|
|
|
// ErrDuplicateTask indicates that another task with the same unique key holds the uniqueness lock.
|
|
ErrDuplicateTask = errors.New("task already exists")
|
|
|
|
// ErrTaskIdConflict indicates that another task with the same task ID already exist
|
|
ErrTaskIdConflict = errors.New("task id conflicts with another task")
|
|
)
|
|
|
|
// TaskNotFoundError indicates that a task with the given ID does not exist
|
|
// in the given queue.
|
|
type TaskNotFoundError struct {
|
|
Queue string // queue name
|
|
ID string // task id
|
|
}
|
|
|
|
func (e *TaskNotFoundError) Error() string {
|
|
return fmt.Sprintf("cannot find task with id=%s in queue %q", e.ID, e.Queue)
|
|
}
|
|
|
|
// IsTaskNotFound reports whether any error in err's chain is of type TaskNotFoundError.
|
|
func IsTaskNotFound(err error) bool {
|
|
var target *TaskNotFoundError
|
|
return As(err, &target)
|
|
}
|
|
|
|
// QueueNotFoundError indicates that a queue with the given name does not exist.
|
|
type QueueNotFoundError struct {
|
|
Queue string // queue name
|
|
}
|
|
|
|
func (e *QueueNotFoundError) Error() string {
|
|
return fmt.Sprintf("queue %q does not exist", e.Queue)
|
|
}
|
|
|
|
// IsQueueNotFound reports whether any error in err's chain is of type QueueNotFoundError.
|
|
func IsQueueNotFound(err error) bool {
|
|
var target *QueueNotFoundError
|
|
return As(err, &target)
|
|
}
|
|
|
|
// QueueNotEmptyError indicates that the given queue is not empty.
|
|
type QueueNotEmptyError struct {
|
|
Queue string // queue name
|
|
}
|
|
|
|
func (e *QueueNotEmptyError) Error() string {
|
|
return fmt.Sprintf("queue %q is not empty", e.Queue)
|
|
}
|
|
|
|
// IsQueueNotEmpty reports whether any error in err's chain is of type QueueNotEmptyError.
|
|
func IsQueueNotEmpty(err error) bool {
|
|
var target *QueueNotEmptyError
|
|
return As(err, &target)
|
|
}
|
|
|
|
// TaskAlreadyArchivedError indicates that the task in question is already archived.
|
|
type TaskAlreadyArchivedError struct {
|
|
Queue string // queue name
|
|
ID string // task id
|
|
}
|
|
|
|
func (e *TaskAlreadyArchivedError) Error() string {
|
|
return fmt.Sprintf("task is already archived: id=%s, queue=%s", e.ID, e.Queue)
|
|
}
|
|
|
|
// IsTaskAlreadyArchived reports whether any error in err's chain is of type TaskAlreadyArchivedError.
|
|
func IsTaskAlreadyArchived(err error) bool {
|
|
var target *TaskAlreadyArchivedError
|
|
return As(err, &target)
|
|
}
|
|
|
|
// RedisCommandError indicates that the given redis command returned error.
|
|
type RedisCommandError struct {
|
|
Command string // redis command (e.g. LRANGE, ZADD, etc)
|
|
Err error // underlying error
|
|
}
|
|
|
|
func (e *RedisCommandError) Error() string {
|
|
return fmt.Sprintf("redis command error: %s failed: %v", strings.ToUpper(e.Command), e.Err)
|
|
}
|
|
|
|
func (e *RedisCommandError) Unwrap() error { return e.Err }
|
|
|
|
// IsRedisCommandError reports whether any error in err's chain is of type RedisCommandError.
|
|
func IsRedisCommandError(err error) bool {
|
|
var target *RedisCommandError
|
|
return As(err, &target)
|
|
}
|
|
|
|
/*************************************************
|
|
Standard Library errors package functions
|
|
*************************************************/
|
|
|
|
// New returns an error that formats as the given text.
|
|
// Each call to New returns a distinct error value even if the text is identical.
|
|
//
|
|
// This function is the errors.New function from the standard libarary (https://golang.org/pkg/errors/#New).
|
|
// It is exported from this package for import convinience.
|
|
func New(text string) error { return errors.New(text) }
|
|
|
|
// Is reports whether any error in err's chain matches target.
|
|
//
|
|
// This function is the errors.Is function from the standard libarary (https://golang.org/pkg/errors/#Is).
|
|
// It is exported from this package for import convinience.
|
|
func Is(err, target error) bool { return errors.Is(err, target) }
|
|
|
|
// As finds the first error in err's chain that matches target, and if so, sets target to that error value and returns true.
|
|
// Otherwise, it returns false.
|
|
//
|
|
// This function is the errors.As function from the standard libarary (https://golang.org/pkg/errors/#As).
|
|
// It is exported from this package for import convinience.
|
|
func As(err error, target interface{}) bool { return errors.As(err, target) }
|
|
|
|
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error.
|
|
// Otherwise, Unwrap returns nil.
|
|
//
|
|
// This function is the errors.Unwrap function from the standard libarary (https://golang.org/pkg/errors/#Unwrap).
|
|
// It is exported from this package for import convinience.
|
|
func Unwrap(err error) error { return errors.Unwrap(err) }
|